From e30be500c5d1f32ea9f5deb54fcb7e7d35aa44cc Mon Sep 17 00:00:00 2001 From: dimohamdy Date: Sat, 5 Aug 2023 00:49:41 +0200 Subject: [PATCH] Use SwiftLeakCheck SPM --- MemoryLeak.xcodeproj/project.pbxproj | 176 +--- .../xcshareddata/swiftpm/Package.resolved | 15 +- .../IDEFindNavigatorScopes.plist | 5 + .../xcschemes/MemoryLeak.xcscheme | 2 +- .../SwiftLeakCheck/BackwardCompatiblity.swift | 12 - .../BaseSyntaxTreeLeakDetector.swift | 24 - .../SwiftLeakCheck/DirectoryScanner.swift | 48 - .../Sources/SwiftLeakCheck/Function.swift | 50 - .../SwiftLeakCheck/FunctionSignature.swift | 172 ---- MemoryLeak/Sources/SwiftLeakCheck/Graph.swift | 857 ------------------ .../Sources/SwiftLeakCheck/GraphBuilder.swift | 199 ---- .../SwiftLeakCheck/GraphLeakDetector.swift | 109 --- MemoryLeak/Sources/SwiftLeakCheck/Leak.swift | 62 -- .../Sources/SwiftLeakCheck/LeakDetector.swift | 26 - .../NonEscapeRules/CollectionRules.swift | 181 ---- .../NonEscapeRules/DispatchQueueRule.swift | 118 --- .../NonEscapeRules/ExprSyntaxPredicate.swift | 104 --- .../NonEscapeRules/NonEscapeRule.swift | 44 - .../NonEscapeRules/UIViewAnimationRule.swift | 86 -- .../UIViewControllerAnimationRule.swift | 128 --- MemoryLeak/Sources/SwiftLeakCheck/Scope.swift | 332 ------- .../SwiftLeakCheck/SourceFileScope.swift | 16 - MemoryLeak/Sources/SwiftLeakCheck/Stack.swift | 58 -- .../SwiftSyntax+Extensions.swift | 263 ------ .../Sources/SwiftLeakCheck/Symbol.swift | 30 - .../SwiftLeakCheck/SyntaxRetrieval.swift | 20 - .../Sources/SwiftLeakCheck/TypeDecl.swift | 32 - .../Sources/SwiftLeakCheck/TypeResolve.swift | 76 -- .../Sources/SwiftLeakCheck/Utility.swift | 20 - .../Sources/SwiftLeakCheck/Variable.swift | 329 ------- .../{Sources/SwiftLeakChecker => }/main.swift | 12 +- 31 files changed, 38 insertions(+), 3568 deletions(-) create mode 100644 MemoryLeak.xcodeproj/project.xcworkspace/xcuserdata/dabdelaziz.xcuserdatad/IDEFindNavigatorScopes.plist delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/BackwardCompatiblity.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/BaseSyntaxTreeLeakDetector.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/DirectoryScanner.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/Function.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/FunctionSignature.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/Graph.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/GraphBuilder.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/GraphLeakDetector.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/Leak.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/LeakDetector.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/CollectionRules.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/DispatchQueueRule.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/ExprSyntaxPredicate.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/NonEscapeRule.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/UIViewAnimationRule.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/UIViewControllerAnimationRule.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/Scope.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/SourceFileScope.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/Stack.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/SwiftSyntax+Extensions.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/Symbol.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/SyntaxRetrieval.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/TypeDecl.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/TypeResolve.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/Utility.swift delete mode 100644 MemoryLeak/Sources/SwiftLeakCheck/Variable.swift rename MemoryLeak/{Sources/SwiftLeakChecker => }/main.swift (83%) diff --git a/MemoryLeak.xcodeproj/project.pbxproj b/MemoryLeak.xcodeproj/project.pbxproj index fb546c2..a0e6ceb 100644 --- a/MemoryLeak.xcodeproj/project.pbxproj +++ b/MemoryLeak.xcodeproj/project.pbxproj @@ -8,35 +8,7 @@ /* Begin PBXBuildFile section */ 33971E4A2A7C692300058593 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E2D2A7C692300058593 /* main.swift */; }; - 33971E4B2A7C692300058593 /* Scope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E2F2A7C692300058593 /* Scope.swift */; }; - 33971E4C2A7C692300058593 /* GraphLeakDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E302A7C692300058593 /* GraphLeakDetector.swift */; }; - 33971E4D2A7C692300058593 /* Leak.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E312A7C692300058593 /* Leak.swift */; }; - 33971E4E2A7C692300058593 /* DirectoryScanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E322A7C692300058593 /* DirectoryScanner.swift */; }; - 33971E4F2A7C692300058593 /* Stack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E332A7C692300058593 /* Stack.swift */; }; - 33971E502A7C692300058593 /* BackwardCompatiblity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E342A7C692300058593 /* BackwardCompatiblity.swift */; }; - 33971E512A7C692300058593 /* SwiftSyntax+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E352A7C692300058593 /* SwiftSyntax+Extensions.swift */; }; - 33971E522A7C692300058593 /* FunctionSignature.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E362A7C692300058593 /* FunctionSignature.swift */; }; - 33971E532A7C692300058593 /* GraphBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E372A7C692300058593 /* GraphBuilder.swift */; }; - 33971E542A7C692300058593 /* TypeResolve.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E382A7C692300058593 /* TypeResolve.swift */; }; - 33971E552A7C692300058593 /* SourceFileScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E392A7C692300058593 /* SourceFileScope.swift */; }; - 33971E562A7C692300058593 /* Graph.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E3A2A7C692300058593 /* Graph.swift */; }; - 33971E572A7C692300058593 /* SyntaxRetrieval.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E3B2A7C692300058593 /* SyntaxRetrieval.swift */; }; - 33971E582A7C692300058593 /* UIViewControllerAnimationRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E3D2A7C692300058593 /* UIViewControllerAnimationRule.swift */; }; - 33971E592A7C692300058593 /* UIViewAnimationRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E3E2A7C692300058593 /* UIViewAnimationRule.swift */; }; - 33971E5A2A7C692300058593 /* CollectionRules.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E3F2A7C692300058593 /* CollectionRules.swift */; }; - 33971E5B2A7C692300058593 /* NonEscapeRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E402A7C692300058593 /* NonEscapeRule.swift */; }; - 33971E5C2A7C692300058593 /* ExprSyntaxPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E412A7C692300058593 /* ExprSyntaxPredicate.swift */; }; - 33971E5D2A7C692300058593 /* DispatchQueueRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E422A7C692300058593 /* DispatchQueueRule.swift */; }; - 33971E5E2A7C692300058593 /* BaseSyntaxTreeLeakDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E432A7C692300058593 /* BaseSyntaxTreeLeakDetector.swift */; }; - 33971E5F2A7C692300058593 /* TypeDecl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E442A7C692300058593 /* TypeDecl.swift */; }; - 33971E602A7C692300058593 /* Utility.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E452A7C692300058593 /* Utility.swift */; }; - 33971E612A7C692300058593 /* Variable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E462A7C692300058593 /* Variable.swift */; }; - 33971E622A7C692300058593 /* Function.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E472A7C692300058593 /* Function.swift */; }; - 33971E632A7C692300058593 /* LeakDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E482A7C692300058593 /* LeakDetector.swift */; }; - 33971E642A7C692300058593 /* Symbol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33971E492A7C692300058593 /* Symbol.swift */; }; - 33971E672A7C696E00058593 /* SwiftParser in Frameworks */ = {isa = PBXBuildFile; productRef = 33971E662A7C696E00058593 /* SwiftParser */; }; - 33971E692A7C696E00058593 /* SwiftSyntax in Frameworks */ = {isa = PBXBuildFile; productRef = 33971E682A7C696E00058593 /* SwiftSyntax */; }; - 33971E6B2A7C696E00058593 /* SwiftSyntaxParser in Frameworks */ = {isa = PBXBuildFile; productRef = 33971E6A2A7C696E00058593 /* SwiftSyntaxParser */; }; + 33971E6F2A7D9B9800058593 /* SwiftLeakCheck in Frameworks */ = {isa = PBXBuildFile; productRef = 33971E6E2A7D9B9800058593 /* SwiftLeakCheck */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -54,32 +26,6 @@ /* Begin PBXFileReference section */ 33971E212A7C690900058593 /* MemoryLeak */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = MemoryLeak; sourceTree = BUILT_PRODUCTS_DIR; }; 33971E2D2A7C692300058593 /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; }; - 33971E2F2A7C692300058593 /* Scope.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Scope.swift; sourceTree = ""; }; - 33971E302A7C692300058593 /* GraphLeakDetector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphLeakDetector.swift; sourceTree = ""; }; - 33971E312A7C692300058593 /* Leak.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Leak.swift; sourceTree = ""; }; - 33971E322A7C692300058593 /* DirectoryScanner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectoryScanner.swift; sourceTree = ""; }; - 33971E332A7C692300058593 /* Stack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Stack.swift; sourceTree = ""; }; - 33971E342A7C692300058593 /* BackwardCompatiblity.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BackwardCompatiblity.swift; sourceTree = ""; }; - 33971E352A7C692300058593 /* SwiftSyntax+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "SwiftSyntax+Extensions.swift"; sourceTree = ""; }; - 33971E362A7C692300058593 /* FunctionSignature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionSignature.swift; sourceTree = ""; }; - 33971E372A7C692300058593 /* GraphBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphBuilder.swift; sourceTree = ""; }; - 33971E382A7C692300058593 /* TypeResolve.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeResolve.swift; sourceTree = ""; }; - 33971E392A7C692300058593 /* SourceFileScope.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SourceFileScope.swift; sourceTree = ""; }; - 33971E3A2A7C692300058593 /* Graph.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Graph.swift; sourceTree = ""; }; - 33971E3B2A7C692300058593 /* SyntaxRetrieval.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SyntaxRetrieval.swift; sourceTree = ""; }; - 33971E3D2A7C692300058593 /* UIViewControllerAnimationRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewControllerAnimationRule.swift; sourceTree = ""; }; - 33971E3E2A7C692300058593 /* UIViewAnimationRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewAnimationRule.swift; sourceTree = ""; }; - 33971E3F2A7C692300058593 /* CollectionRules.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionRules.swift; sourceTree = ""; }; - 33971E402A7C692300058593 /* NonEscapeRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NonEscapeRule.swift; sourceTree = ""; }; - 33971E412A7C692300058593 /* ExprSyntaxPredicate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExprSyntaxPredicate.swift; sourceTree = ""; }; - 33971E422A7C692300058593 /* DispatchQueueRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DispatchQueueRule.swift; sourceTree = ""; }; - 33971E432A7C692300058593 /* BaseSyntaxTreeLeakDetector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseSyntaxTreeLeakDetector.swift; sourceTree = ""; }; - 33971E442A7C692300058593 /* TypeDecl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TypeDecl.swift; sourceTree = ""; }; - 33971E452A7C692300058593 /* Utility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Utility.swift; sourceTree = ""; }; - 33971E462A7C692300058593 /* Variable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Variable.swift; sourceTree = ""; }; - 33971E472A7C692300058593 /* Function.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Function.swift; sourceTree = ""; }; - 33971E482A7C692300058593 /* LeakDetector.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LeakDetector.swift; sourceTree = ""; }; - 33971E492A7C692300058593 /* Symbol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Symbol.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -87,9 +33,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 33971E6B2A7C696E00058593 /* SwiftSyntaxParser in Frameworks */, - 33971E672A7C696E00058593 /* SwiftParser in Frameworks */, - 33971E692A7C696E00058593 /* SwiftSyntax in Frameworks */, + 33971E6F2A7D9B9800058593 /* SwiftLeakCheck in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -113,69 +57,11 @@ sourceTree = ""; }; 33971E232A7C690900058593 /* MemoryLeak */ = { - isa = PBXGroup; - children = ( - 33971E2B2A7C692300058593 /* Sources */, - ); - path = MemoryLeak; - sourceTree = ""; - }; - 33971E2B2A7C692300058593 /* Sources */ = { - isa = PBXGroup; - children = ( - 33971E2C2A7C692300058593 /* SwiftLeakChecker */, - 33971E2E2A7C692300058593 /* SwiftLeakCheck */, - ); - path = Sources; - sourceTree = ""; - }; - 33971E2C2A7C692300058593 /* SwiftLeakChecker */ = { isa = PBXGroup; children = ( 33971E2D2A7C692300058593 /* main.swift */, ); - path = SwiftLeakChecker; - sourceTree = ""; - }; - 33971E2E2A7C692300058593 /* SwiftLeakCheck */ = { - isa = PBXGroup; - children = ( - 33971E2F2A7C692300058593 /* Scope.swift */, - 33971E302A7C692300058593 /* GraphLeakDetector.swift */, - 33971E312A7C692300058593 /* Leak.swift */, - 33971E322A7C692300058593 /* DirectoryScanner.swift */, - 33971E332A7C692300058593 /* Stack.swift */, - 33971E342A7C692300058593 /* BackwardCompatiblity.swift */, - 33971E352A7C692300058593 /* SwiftSyntax+Extensions.swift */, - 33971E362A7C692300058593 /* FunctionSignature.swift */, - 33971E372A7C692300058593 /* GraphBuilder.swift */, - 33971E382A7C692300058593 /* TypeResolve.swift */, - 33971E392A7C692300058593 /* SourceFileScope.swift */, - 33971E3A2A7C692300058593 /* Graph.swift */, - 33971E3B2A7C692300058593 /* SyntaxRetrieval.swift */, - 33971E3C2A7C692300058593 /* NonEscapeRules */, - 33971E432A7C692300058593 /* BaseSyntaxTreeLeakDetector.swift */, - 33971E442A7C692300058593 /* TypeDecl.swift */, - 33971E452A7C692300058593 /* Utility.swift */, - 33971E462A7C692300058593 /* Variable.swift */, - 33971E472A7C692300058593 /* Function.swift */, - 33971E482A7C692300058593 /* LeakDetector.swift */, - 33971E492A7C692300058593 /* Symbol.swift */, - ); - path = SwiftLeakCheck; - sourceTree = ""; - }; - 33971E3C2A7C692300058593 /* NonEscapeRules */ = { - isa = PBXGroup; - children = ( - 33971E3D2A7C692300058593 /* UIViewControllerAnimationRule.swift */, - 33971E3E2A7C692300058593 /* UIViewAnimationRule.swift */, - 33971E3F2A7C692300058593 /* CollectionRules.swift */, - 33971E402A7C692300058593 /* NonEscapeRule.swift */, - 33971E412A7C692300058593 /* ExprSyntaxPredicate.swift */, - 33971E422A7C692300058593 /* DispatchQueueRule.swift */, - ); - path = NonEscapeRules; + path = MemoryLeak; sourceTree = ""; }; /* End PBXGroup section */ @@ -195,9 +81,7 @@ ); name = MemoryLeak; packageProductDependencies = ( - 33971E662A7C696E00058593 /* SwiftParser */, - 33971E682A7C696E00058593 /* SwiftSyntax */, - 33971E6A2A7C696E00058593 /* SwiftSyntaxParser */, + 33971E6E2A7D9B9800058593 /* SwiftLeakCheck */, ); productName = MemoryLeak; productReference = 33971E212A7C690900058593 /* MemoryLeak */; @@ -228,7 +112,7 @@ ); mainGroup = 33971E182A7C690900058593; packageReferences = ( - 33971E652A7C696E00058593 /* XCRemoteSwiftPackageReference "swift-syntax" */, + 33971E6D2A7D9B9800058593 /* XCRemoteSwiftPackageReference "swift-leak-check" */, ); productRefGroup = 33971E222A7C690900058593 /* Products */; projectDirPath = ""; @@ -244,33 +128,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 33971E512A7C692300058593 /* SwiftSyntax+Extensions.swift in Sources */, - 33971E572A7C692300058593 /* SyntaxRetrieval.swift in Sources */, - 33971E4D2A7C692300058593 /* Leak.swift in Sources */, - 33971E562A7C692300058593 /* Graph.swift in Sources */, - 33971E542A7C692300058593 /* TypeResolve.swift in Sources */, 33971E4A2A7C692300058593 /* main.swift in Sources */, - 33971E5E2A7C692300058593 /* BaseSyntaxTreeLeakDetector.swift in Sources */, - 33971E4F2A7C692300058593 /* Stack.swift in Sources */, - 33971E552A7C692300058593 /* SourceFileScope.swift in Sources */, - 33971E522A7C692300058593 /* FunctionSignature.swift in Sources */, - 33971E5A2A7C692300058593 /* CollectionRules.swift in Sources */, - 33971E532A7C692300058593 /* GraphBuilder.swift in Sources */, - 33971E4E2A7C692300058593 /* DirectoryScanner.swift in Sources */, - 33971E582A7C692300058593 /* UIViewControllerAnimationRule.swift in Sources */, - 33971E4B2A7C692300058593 /* Scope.swift in Sources */, - 33971E622A7C692300058593 /* Function.swift in Sources */, - 33971E592A7C692300058593 /* UIViewAnimationRule.swift in Sources */, - 33971E5C2A7C692300058593 /* ExprSyntaxPredicate.swift in Sources */, - 33971E632A7C692300058593 /* LeakDetector.swift in Sources */, - 33971E502A7C692300058593 /* BackwardCompatiblity.swift in Sources */, - 33971E5D2A7C692300058593 /* DispatchQueueRule.swift in Sources */, - 33971E602A7C692300058593 /* Utility.swift in Sources */, - 33971E5F2A7C692300058593 /* TypeDecl.swift in Sources */, - 33971E4C2A7C692300058593 /* GraphLeakDetector.swift in Sources */, - 33971E642A7C692300058593 /* Symbol.swift in Sources */, - 33971E5B2A7C692300058593 /* NonEscapeRule.swift in Sources */, - 33971E612A7C692300058593 /* Variable.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -432,31 +290,21 @@ /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ - 33971E652A7C696E00058593 /* XCRemoteSwiftPackageReference "swift-syntax" */ = { + 33971E6D2A7D9B9800058593 /* XCRemoteSwiftPackageReference "swift-leak-check" */ = { isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/apple/swift-syntax.git"; + repositoryURL = "https://github.com/dimohamdy/swift-leak-check"; requirement = { - kind = upToNextMajorVersion; - minimumVersion = 508.0.0; + branch = master; + kind = branch; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ - 33971E662A7C696E00058593 /* SwiftParser */ = { - isa = XCSwiftPackageProductDependency; - package = 33971E652A7C696E00058593 /* XCRemoteSwiftPackageReference "swift-syntax" */; - productName = SwiftParser; - }; - 33971E682A7C696E00058593 /* SwiftSyntax */ = { - isa = XCSwiftPackageProductDependency; - package = 33971E652A7C696E00058593 /* XCRemoteSwiftPackageReference "swift-syntax" */; - productName = SwiftSyntax; - }; - 33971E6A2A7C696E00058593 /* SwiftSyntaxParser */ = { + 33971E6E2A7D9B9800058593 /* SwiftLeakCheck */ = { isa = XCSwiftPackageProductDependency; - package = 33971E652A7C696E00058593 /* XCRemoteSwiftPackageReference "swift-syntax" */; - productName = SwiftSyntaxParser; + package = 33971E6D2A7D9B9800058593 /* XCRemoteSwiftPackageReference "swift-leak-check" */; + productName = SwiftLeakCheck; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/MemoryLeak.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/MemoryLeak.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d16d523..c4845ae 100644 --- a/MemoryLeak.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/MemoryLeak.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,12 +1,21 @@ { "pins" : [ + { + "identity" : "swift-leak-check", + "kind" : "remoteSourceControl", + "location" : "https://github.com/dimohamdy/swift-leak-check", + "state" : { + "branch" : "master", + "revision" : "88e4364b4cec6fd78afd2e212c7a510e83af9b90" + } + }, { "identity" : "swift-syntax", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax.git", + "location" : "https://github.com/apple/swift-syntax", "state" : { - "revision" : "2c49d66d34dfd6f8130afdba889de77504b58ec0", - "version" : "508.0.1" + "revision" : "cd793adf5680e138bf2bcbaacc292490175d0dcd", + "version" : "508.0.0" } } ], diff --git a/MemoryLeak.xcodeproj/project.xcworkspace/xcuserdata/dabdelaziz.xcuserdatad/IDEFindNavigatorScopes.plist b/MemoryLeak.xcodeproj/project.xcworkspace/xcuserdata/dabdelaziz.xcuserdatad/IDEFindNavigatorScopes.plist new file mode 100644 index 0000000..5dd5da8 --- /dev/null +++ b/MemoryLeak.xcodeproj/project.xcworkspace/xcuserdata/dabdelaziz.xcuserdatad/IDEFindNavigatorScopes.plist @@ -0,0 +1,5 @@ + + + + + diff --git a/MemoryLeak.xcodeproj/xcshareddata/xcschemes/MemoryLeak.xcscheme b/MemoryLeak.xcodeproj/xcshareddata/xcschemes/MemoryLeak.xcscheme index 137412c..ff02070 100644 --- a/MemoryLeak.xcodeproj/xcshareddata/xcschemes/MemoryLeak.xcscheme +++ b/MemoryLeak.xcodeproj/xcshareddata/xcschemes/MemoryLeak.xcscheme @@ -30,7 +30,7 @@ shouldAutocreateTestPlan = "YES"> [Leak] { - let node = try SyntaxRetrieval.request(content: content) - return detect(node) - } - - open func detect(_ sourceFileNode: SourceFileSyntax) -> [Leak] { - fatalError("Implemented by subclass") - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/DirectoryScanner.swift b/MemoryLeak/Sources/SwiftLeakCheck/DirectoryScanner.swift deleted file mode 100644 index 0cef78f..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/DirectoryScanner.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// DirectoryScanner.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 09/12/2019. -// - -import Foundation - -public final class DirectoryScanner { - private let callback: (URL, inout Bool) -> Void - private var shouldStop = false - - public init(callback: @escaping (URL, inout Bool) -> Void) { - self.callback = callback - } - - public func scan(url: URL) { - if shouldStop { - shouldStop = false // Clear - return - } - - let isDirectory = (try? url.resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory ?? false - if !isDirectory { - callback(url, &shouldStop) - } else { - let enumerator = FileManager.default.enumerator( - at: url, - includingPropertiesForKeys: nil, - options: [.skipsSubdirectoryDescendants], - errorHandler: nil - )! - - for childPath in enumerator { - if let url = childPath as? URL { - scan(url: url) - if shouldStop { - return - } - } - } - } - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/Function.swift b/MemoryLeak/Sources/SwiftLeakCheck/Function.swift deleted file mode 100644 index 85fe7e8..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/Function.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// Function.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 18/11/2019. -// - -import SwiftSyntax - -public typealias Function = FunctionDeclSyntax - -public extension Function { - enum MatchResult: Equatable { - public struct MappingInfo: Equatable { - let argumentToParamMapping: [FunctionCallArgumentSyntax: FunctionParameterSyntax] - let trailingClosureArgumentToParam: FunctionParameterSyntax? - } - - case nameMismatch - case argumentMismatch - case matched(MappingInfo) - - var isMatched: Bool { - switch self { - case .nameMismatch, - .argumentMismatch: - return false - case .matched: - return true - } - } - } - - func match(_ functionCallExpr: FunctionCallExprSyntax) -> MatchResult { - let (signature, mapping) = FunctionSignature.from(functionDeclExpr: self) - switch signature.match(functionCallExpr) { - case .nameMismatch: - return .nameMismatch - case .argumentMismatch: - return .argumentMismatch - case .matched(let matchedInfo): - return .matched(.init( - argumentToParamMapping: matchedInfo.argumentToParamMapping.mapValues { mapping[$0]! }, - trailingClosureArgumentToParam: matchedInfo.trailingClosureArgumentToParam.flatMap { mapping[$0] })) - } - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/FunctionSignature.swift b/MemoryLeak/Sources/SwiftLeakCheck/FunctionSignature.swift deleted file mode 100644 index 8e869bf..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/FunctionSignature.swift +++ /dev/null @@ -1,172 +0,0 @@ -// -// FunctionSignature.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 15/12/2019. -// - -import SwiftSyntax - -public struct FunctionSignature { - public let funcName: String - public let params: [FunctionParam] - - public init(name: String, params: [FunctionParam]) { - self.funcName = name - self.params = params - } - - public static func from(functionDeclExpr: FunctionDeclSyntax) -> (FunctionSignature, [FunctionParam: FunctionParameterSyntax]) { - let funcName = functionDeclExpr.identifier.text - let params = functionDeclExpr.signature.input.parameterList.map { FunctionParam(param: $0) } - let mapping = Dictionary(uniqueKeysWithValues: zip(params, functionDeclExpr.signature.input.parameterList)) - return (FunctionSignature(name: funcName, params: params), mapping) - } - - public enum MatchResult: Equatable { - public struct MappingInfo: Equatable { - let argumentToParamMapping: [FunctionCallArgumentSyntax: FunctionParam] - let trailingClosureArgumentToParam: FunctionParam? - } - - case nameMismatch - case argumentMismatch - case matched(MappingInfo) - - var isMatched: Bool { - switch self { - case .nameMismatch, - .argumentMismatch: - return false - case .matched: - return true - } - } - } - - public func match(_ functionCallExpr: FunctionCallExprSyntax) -> MatchResult { - guard funcName == functionCallExpr.symbol?.text else { - return .nameMismatch - } - - //print("Debug: \(functionCallExpr)") - - return match((ArgumentListWrapper(functionCallExpr.argumentList), functionCallExpr.trailingClosure)) - } - - private func match(_ rhs: (ArgumentListWrapper, ClosureExprSyntax?)) -> MatchResult { - let (arguments, trailingClosure) = rhs - - guard params.count > 0 else { - if arguments.count == 0 && trailingClosure == nil { - return .matched(.init(argumentToParamMapping: [:], trailingClosureArgumentToParam: nil)) - } else { - return .argumentMismatch - } - } - - let firstParam = params[0] - if firstParam.canOmit { - let matchResult = removingFirstParam().match(rhs) - if matchResult.isMatched { - return matchResult - } - } - - guard arguments.count > 0 else { - // In order to match, trailingClosure must be firstParam, there're no more params - guard let trailingClosure = trailingClosure else { - return .argumentMismatch - } - if params.count > 1 { - return .argumentMismatch - } - if isMatched(param: firstParam, trailingClosure: trailingClosure) { - return .matched(.init(argumentToParamMapping: [:], trailingClosureArgumentToParam: firstParam)) - } else { - return .argumentMismatch - } - } - - let firstArgument = arguments[0] - guard isMatched(param: firstParam, argument: firstArgument) else { - return .argumentMismatch - } - - let matchResult = removingFirstParam().match((arguments.removingFirst(), trailingClosure)) - if case let .matched(matchInfo) = matchResult { - var argumentToParamMapping = matchInfo.argumentToParamMapping - argumentToParamMapping[firstArgument] = firstParam - return .matched(.init(argumentToParamMapping: argumentToParamMapping, trailingClosureArgumentToParam: matchInfo.trailingClosureArgumentToParam)) - } else { - return .argumentMismatch - } - } - - // TODO: type matching - private func isMatched(param: FunctionParam, argument: FunctionCallArgumentSyntax) -> Bool { - return param.name == argument.label?.text - } - private func isMatched(param: FunctionParam, trailingClosure: ClosureExprSyntax) -> Bool { - return param.isClosure - } - - private func removingFirstParam() -> FunctionSignature { - return FunctionSignature(name: funcName, params: Array(params[1...])) - } -} - -public struct FunctionParam: Hashable { - public let name: String? - public let secondName: String? // This acts as a way to differentiate param when name is omitted. Don't remove this - public let canOmit: Bool - public let isClosure: Bool - - public init(name: String?, - secondName: String? = nil, - isClosure: Bool = false, - canOmit: Bool = false) { - assert(name != "_") - self.name = name - self.secondName = secondName - self.isClosure = isClosure - self.canOmit = canOmit - } - - public init(param: FunctionParameterSyntax) { - name = (param.firstName?.text == "_" ? nil : param.firstName?.text) - secondName = param.secondName?.text - - isClosure = (param.type?.isClosure == true) - canOmit = param.defaultArgument != nil - } -} - -private struct ArgumentListWrapper { - let argumentList: FunctionCallArgumentListSyntax - private let startIndex: Int - - init(_ argumentList: FunctionCallArgumentListSyntax) { - self.init(argumentList, startIndex: 0) - } - - private init(_ argumentList: FunctionCallArgumentListSyntax, startIndex: Int) { - self.argumentList = argumentList - self.startIndex = startIndex - } - - func removingFirst() -> ArgumentListWrapper { - return ArgumentListWrapper(argumentList, startIndex: startIndex + 1) - } - - subscript(_ i: Int) -> FunctionCallArgumentSyntax { - return argumentList[startIndex + i] - } - - var count: Int { - return argumentList.count - startIndex - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/Graph.swift b/MemoryLeak/Sources/SwiftLeakCheck/Graph.swift deleted file mode 100644 index b34048b..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/Graph.swift +++ /dev/null @@ -1,857 +0,0 @@ -// -// Graph.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 11/11/2019. -// - -import SwiftSyntax - -public protocol Graph { - var sourceFileScope: SourceFileScope { get } - - /// Return the corresponding scope of a node if the node is of scope-type (class, func, closure,...) - /// or return the enclosing scope if the node is not scope-type - /// - Parameter node: The node - func scope(for node: Syntax) -> Scope - - /// Get the scope that encloses a given node - /// Eg, Scopes that enclose a func could be class, enum,... - /// Or scopes that enclose a statement could be func, closure,... - /// If the node is not enclosed by a scope (eg, sourcefile node), return the scope of the node itself - /// - Parameter node: A node - /// - Returns: The scope that encloses the node - func enclosingScope(for node: Syntax) -> Scope - - /// Return the TypeDecl that encloses a given node - /// - Parameter node: given node - func enclosingTypeDecl(for node: Syntax) -> TypeDecl? - - /// Find the nearest scope to a symbol, that can resolve the definition of that symbol - /// Usually it is the enclosing scope of the symbol - func closetScopeThatCanResolveSymbol(_ symbol: Symbol) -> Scope? - - func resolveExprType(_ expr: ExprSyntax) -> TypeResolve - func resolveVariableType(_ variable: Variable) -> TypeResolve - func resolveType(_ type: TypeSyntax) -> TypeResolve - func getAllRelatedTypeDecls(from typeDecl: TypeDecl) -> [TypeDecl] - func getAllRelatedTypeDecls(from typeResolve: TypeResolve) -> [TypeDecl] - - func resolveVariable(_ identifier: IdentifierExprSyntax) -> Variable? - func getVariableReferences(variable: Variable) -> [IdentifierExprSyntax] - - func resolveFunction(_ funcCallExpr: FunctionCallExprSyntax) -> (Function, Function.MatchResult.MappingInfo)? - - func isClosureEscape(_ closure: ClosureExprSyntax, nonEscapeRules: [NonEscapeRule]) -> Bool - func isCollection(_ node: ExprSyntax) -> Bool -} - -final class GraphImpl: Graph { - enum SymbolResolve { - case variable(Variable) - case function(Function) - case typeDecl(TypeDecl) - - var variable: Variable? { - switch self { - case .variable(let variable): return variable - default: - return nil - } - } - } - - private var mapScopeNodeToScope = [ScopeNode: Scope]() - private var cachedSymbolResolved = [Symbol: SymbolResolve]() - private var cachedReferencesToVariable = [Variable: [IdentifierExprSyntax]]() - private var cachedVariableType = [Variable: TypeResolve]() - private var cachedFunCallExprType = [FunctionCallExprSyntax: TypeResolve]() - private var cachedClosureEscapeCheck = [ClosureExprSyntax: Bool]() - - let sourceFileScope: SourceFileScope - init(sourceFileScope: SourceFileScope) { - self.sourceFileScope = sourceFileScope - buildScopeNodeToScopeMapping(root: sourceFileScope) - } - - private func buildScopeNodeToScopeMapping(root: Scope) { - mapScopeNodeToScope[root.scopeNode] = root - root.childScopes.forEach { child in - buildScopeNodeToScopeMapping(root: child) - } - } -} - -// MARK: - Scope -extension GraphImpl { - func scope(for node: Syntax) -> Scope { - guard let scopeNode = ScopeNode.from(node: node) else { - return enclosingScope(for: node) - } - - return scope(for: scopeNode) - } - - func enclosingScope(for node: Syntax) -> Scope { - guard let scopeNode = node.enclosingScopeNode else { - let result = scope(for: node) - assert(result == sourceFileScope) - return result - } - - return scope(for: scopeNode) - } - - func enclosingTypeDecl(for node: Syntax) -> TypeDecl? { - var scopeNode: ScopeNode! = node.enclosingScopeNode - while scopeNode != nil { - if scopeNode.type.isTypeDecl { - return scope(for: scopeNode).typeDecl - } - scopeNode = scopeNode.enclosingScopeNode - } - - return nil - } - - func scope(for scopeNode: ScopeNode) -> Scope { - guard let result = mapScopeNodeToScope[scopeNode] else { - fatalError("Can't find the scope of node \(scopeNode)") - } - return result - } - - func closetScopeThatCanResolveSymbol(_ symbol: Symbol) -> Scope? { - let scope = enclosingScope(for: symbol.node) - // Special case when node is a closure capture item, ie `{ [weak self] in` - // We need to examine node wrt closure's parent - if symbol.node.parent?.is(ClosureCaptureItemSyntax.self) == true { - if let parentScope = scope.parent { - return parentScope - } else { - fatalError("Can't happen") - } - } - - if symbol.node.hasAncestor({ $0.is(InheritedTypeSyntax.self) }) { - return scope.parent - } - - if symbol.node.hasAncestor({ $0.is(ExtensionDeclSyntax.self) && symbol.node.isDescendent(of: $0.as(ExtensionDeclSyntax.self)!.extendedType._syntaxNode) }) { - return scope.parent - } - - return scope - } -} - -// MARK: - Symbol resolve -extension GraphImpl { - enum ResolveSymbolOption: Equatable, CaseIterable { - case function - case variable - case typeDecl - } - - func _findSymbol(_ symbol: Symbol, - options: [ResolveSymbolOption] = ResolveSymbolOption.allCases, - onResult: (SymbolResolve) -> Bool) -> SymbolResolve? { - var scope: Scope! = closetScopeThatCanResolveSymbol(symbol) - while scope != nil { - if let result = cachedSymbolResolved[symbol], onResult(result) { - return result - } - - if let result = _findSymbol(symbol, options: options, in: scope, onResult: onResult) { - cachedSymbolResolved[symbol] = result - return result - } - - scope = scope?.parent - } - - return nil - } - - func _findSymbol(_ symbol: Symbol, - options: [ResolveSymbolOption] = ResolveSymbolOption.allCases, - in scope: Scope, - onResult: (SymbolResolve) -> Bool) -> SymbolResolve? { - - let scopesWithRelatedTypeDecl: [Scope] - if let typeDecl = scope.typeDecl { - scopesWithRelatedTypeDecl = getAllRelatedTypeDecls(from: typeDecl).map { $0.scope } - } else { - scopesWithRelatedTypeDecl = [scope] - } - - for scope in scopesWithRelatedTypeDecl { - if options.contains(.variable) { - if case let .identifier(node) = symbol, let variable = scope.getVariable(node) { - let result: SymbolResolve = .variable(variable) - if onResult(result) { - cachedReferencesToVariable[variable] = (cachedReferencesToVariable[variable] ?? []) + [node] - return result - } - } - } - - if options.contains(.function) { - for function in scope.getFunctionWithSymbol(symbol) { - let result: SymbolResolve = .function(function) - if onResult(result) { - return result - } - } - } - - if options.contains(.typeDecl) { - let typeDecls = scope.getTypeDecl(name: symbol.name) - for typeDecl in typeDecls { - let result: SymbolResolve = .typeDecl(typeDecl) - if onResult(result) { - return result - } - } - } - } - - return nil - } -} - -// MARK: - Variable reference -extension GraphImpl { - - @discardableResult - func resolveVariable(_ identifier: IdentifierExprSyntax) -> Variable? { - return _findSymbol(.identifier(identifier), options: [.variable]) { resolve -> Bool in - if resolve.variable != nil { - return true - } - return false - }?.variable - } - - func getVariableReferences(variable: Variable) -> [IdentifierExprSyntax] { - return cachedReferencesToVariable[variable] ?? [] - } - - func couldReferenceSelf(_ node: ExprSyntax) -> Bool { - if node.isCalledExpr() { - return false - } - - if let identifierNode = node.as(IdentifierExprSyntax.self) { - guard let variable = resolveVariable(identifierNode) else { - return identifierNode.identifier.text == "self" - } - - switch variable.raw { - case .param: - return false - case let .capture(capturedNode): - return couldReferenceSelf(ExprSyntax(capturedNode)) - case let .binding(_, valueNode): - if let valueNode = valueNode { - return couldReferenceSelf(valueNode) - } - return false - } - } - - return false - } -} - -// MARK: - Function resolve -extension GraphImpl { - func resolveFunction(_ funcCallExpr: FunctionCallExprSyntax) -> (Function, Function.MatchResult.MappingInfo)? { - if let identifier = funcCallExpr.calledExpression.as(IdentifierExprSyntax.self) { // doSmth(...) or A(...) - return _findFunction(symbol: .identifier(identifier), funcCallExpr: funcCallExpr) - } - - if let memberAccessExpr = funcCallExpr.calledExpression.as(MemberAccessExprSyntax.self) { // a.doSmth(...) or .doSmth(...) - if let base = memberAccessExpr.base { - if couldReferenceSelf(base) { - return _findFunction(symbol: .token(memberAccessExpr.name), funcCallExpr: funcCallExpr) - } - let typeDecls = getAllRelatedTypeDecls(from: resolveExprType(base)) - return _findFunction(from: typeDecls, symbol: .token(memberAccessExpr.name), funcCallExpr: funcCallExpr) - } else { - // Base is omitted when the type can be inferred. - // For eg, we can say: let s: String = .init(...) - return nil - } - } - - if funcCallExpr.calledExpression.is(OptionalChainingExprSyntax.self) { - // TODO - return nil - } - - // Unhandled case - return nil - } - - // TODO: Currently we only resolve to `func`. This could resole to `closure` as well - private func _findFunction(symbol: Symbol, funcCallExpr: FunctionCallExprSyntax) - -> (Function, Function.MatchResult.MappingInfo)? { - - var result: (Function, Function.MatchResult.MappingInfo)? - _ = _findSymbol(symbol, options: [.function]) { resolve -> Bool in - switch resolve { - case .variable, .typeDecl: // This could be due to cache - return false - case .function(let function): - let mustStop = enclosingScope(for: function._syntaxNode).type.isTypeDecl - - switch function.match(funcCallExpr) { - case .argumentMismatch, - .nameMismatch: - return mustStop - case .matched(let info): - guard result == nil else { - // Should not happenn - assert(false, "ambiguous") - return true // Exit - } - result = (function, info) - #if DEBUG - return mustStop // Continue to search to make sure no ambiguity - #else - return true - #endif - } - } - } - - return result - } - - private func _findFunction(from typeDecls: [TypeDecl], symbol: Symbol, funcCallExpr: FunctionCallExprSyntax) - -> (Function, Function.MatchResult.MappingInfo)? { - - for typeDecl in typeDecls { - for function in typeDecl.scope.getFunctionWithSymbol(symbol) { - if case let .matched(info) = function.match(funcCallExpr) { - return (function, info) - } - } - } - - return nil - } -} - -// MARK: Type resolve -extension GraphImpl { - func resolveVariableType(_ variable: Variable) -> TypeResolve { - if let type = cachedVariableType[variable] { - return type - } - - let result = _resolveType(variable.typeInfo) - cachedVariableType[variable] = result - return result - } - - func resolveExprType(_ expr: ExprSyntax) -> TypeResolve { - if let optionalExpr = expr.as(OptionalChainingExprSyntax.self) { - return .optional(base: resolveExprType(optionalExpr.expression)) - } - - if let identifierExpr = expr.as(IdentifierExprSyntax.self) { - if let variable = resolveVariable(identifierExpr) { - return resolveVariableType(variable) - } - if identifierExpr.identifier.text == "self" { - return enclosingTypeDecl(for: expr._syntaxNode).flatMap { .type($0) } ?? .unknown - } - // May be global variable, or type like Int, String,... - return .unknown - } - -// if let memberAccessExpr = node.as(MemberAccessExprSyntax.self) { -// guard let base = memberAccessExpr.base else { -// fatalError("Is it possible that `base` is nil ?") -// } -// -// } - - if let functionCallExpr = expr.as(FunctionCallExprSyntax.self) { - let result = cachedFunCallExprType[functionCallExpr] ?? _resolveFunctionCallType(functionCallExpr: functionCallExpr) - cachedFunCallExprType[functionCallExpr] = result - return result - } - - if let arrayExpr = expr.as(ArrayExprSyntax.self) { - return .sequence(elementType: resolveExprType(arrayExpr.elements[0].expression)) - } - - if expr.is(DictionaryExprSyntax.self) { - return .dict - } - - if expr.is(IntegerLiteralExprSyntax.self) { - return _getAllExtensions(name: ["Int"]).first.flatMap { .type($0) } ?? .name(["Int"]) - } - if expr.is(StringLiteralExprSyntax.self) { - return _getAllExtensions(name: ["String"]).first.flatMap { .type($0) } ?? .name(["String"]) - } - if expr.is(FloatLiteralExprSyntax.self) { - return _getAllExtensions(name: ["Float"]).first.flatMap { .type($0) } ?? .name(["Float"]) - } - if expr.is(BooleanLiteralExprSyntax.self) { - return _getAllExtensions(name: ["Bool"]).first.flatMap { .type($0) } ?? .name(["Bool"]) - } - - if let tupleExpr = expr.as(TupleExprSyntax.self) { - if tupleExpr.elementList.count == 1, let range = tupleExpr.elementList[0].expression.rangeInfo { - if let leftType = range.left.flatMap({ resolveExprType($0) })?.toNilIfUnknown { - return .sequence(elementType: leftType) - } else if let rightType = range.right.flatMap({ resolveExprType($0) })?.toNilIfUnknown { - return .sequence(elementType: rightType) - } else { - return .unknown - } - } - - return .tuple(tupleExpr.elementList.map { resolveExprType($0.expression) }) - } - - if let subscriptExpr = expr.as(SubscriptExprSyntax.self) { - let sequenceElementType = resolveExprType(subscriptExpr.calledExpression).sequenceElementType - if sequenceElementType != .unknown { - if subscriptExpr.argumentList.count == 1, let argument = subscriptExpr.argumentList.first?.expression { - if argument.rangeInfo != nil { - return .sequence(elementType: sequenceElementType) - } - if resolveExprType(argument).isInt { - return sequenceElementType - } - } - } - - return .unknown - } - - return .unknown - } - - private func _resolveType(_ typeInfo: TypeInfo) -> TypeResolve { - switch typeInfo { - case .exact(let type): - return resolveType(type) - case .inferedFromExpr(let expr): - return resolveExprType(expr) - case .inferedFromClosure(let closureExpr, let paramIndex, let paramCount): - // let x: (X, Y) -> Z = { a,b in ...} - if let closureVariable = enclosingScope(for: Syntax(closureExpr)).getVariableBindingTo(expr: ExprSyntax(closureExpr)) { - switch closureVariable.typeInfo { - case .exact(let type): - guard let argumentsType = (type.as(FunctionTypeSyntax.self))?.arguments else { - // Eg: let onFetchJobs: JobCardsFetcher.OnFetchJobs = { [weak self] jobs in ... } - return .unknown - } - assert(argumentsType.count == paramCount) - return resolveType(argumentsType[paramIndex].type) - case .inferedFromClosure, - .inferedFromExpr, - .inferedFromSequence, - .inferedFromTuple: - assert(false, "Seems wrong") - return .unknown - } - } - // TODO: there's also this case - // var b: ((X) -> Y)! - // b = { x in ... } - return .unknown - case .inferedFromSequence(let sequenceExpr): - let sequenceType = resolveExprType(sequenceExpr) - return sequenceType.sequenceElementType - case .inferedFromTuple(let tupleTypeInfo, let index): - if case let .tuple(types) = _resolveType(tupleTypeInfo) { - return types[index] - } - return .unknown - } - } - - func resolveType(_ type: TypeSyntax) -> TypeResolve { - if type.isOptional { - return .optional(base: resolveType(type.wrappedType)) - } - - if let arrayType = type.as(ArrayTypeSyntax.self) { - return .sequence(elementType: resolveType(arrayType.elementType)) - } - - if type.is(DictionaryTypeSyntax.self) { - return .dict - } - - if let tupleType = type.as(TupleTypeSyntax.self) { - return .tuple(tupleType.elements.map { resolveType($0.type) }) - } - - if let tokens = type.tokens, let typeDecl = resolveTypeDecl(tokens: tokens) { - return .type(typeDecl) - } else if let name = type.name { - return .name(name) - } else { - return .unknown - } - } - - private func _resolveFunctionCallType(functionCallExpr: FunctionCallExprSyntax, ignoreOptional: Bool = false) -> TypeResolve { - - if let (function, _) = resolveFunction(functionCallExpr) { - if let type = function.signature.output?.returnType { - return resolveType(type) - } else { - return .unknown // Void - } - } - - var calledExpr = functionCallExpr.calledExpression - - if let optionalExpr = calledExpr.as(OptionalChainingExprSyntax.self) { // Must be optional closure - if !ignoreOptional { - return .optional(base: _resolveFunctionCallType(functionCallExpr: functionCallExpr, ignoreOptional: true)) - } else { - calledExpr = optionalExpr.expression - } - } - - // [X]() - if let arrayExpr = calledExpr.as(ArrayExprSyntax.self) { - if let typeIdentifier = arrayExpr.elements[0].expression.as(IdentifierExprSyntax.self) { - if let typeDecl = resolveTypeDecl(tokens: [typeIdentifier.identifier]) { - return .sequence(elementType: .type(typeDecl)) - } else { - return .sequence(elementType: .name([typeIdentifier.identifier.text])) - } - } else { - return .sequence(elementType: resolveExprType(arrayExpr.elements[0].expression)) - } - } - - // [X: Y]() - if calledExpr.is(DictionaryExprSyntax.self) { - return .dict - } - - // doSmth() or A() - if let identifierExpr = calledExpr.as(IdentifierExprSyntax.self) { - let identifierResolve = _findSymbol(.identifier(identifierExpr)) { resolve in - switch resolve { - case .function(let function): - return function.match(functionCallExpr).isMatched - case .typeDecl: - return true - case .variable: - return false - } - } - if let identifierResolve = identifierResolve { - switch identifierResolve { - // doSmth() - case .function(let function): - let returnType = function.signature.output?.returnType - return returnType.flatMap { resolveType($0) } ?? .unknown - // A() - case .typeDecl(let typeDecl): - return .type(typeDecl) - case .variable: - break - } - } - } - - // x.y() - if let memberAccessExpr = calledExpr.as(MemberAccessExprSyntax.self) { - if let base = memberAccessExpr.base { - let baseType = resolveExprType(base) - if _isCollection(baseType) { - let funcName = memberAccessExpr.name.text - if ["map", "flatMap", "compactMap", "enumerated"].contains(funcName) { - return .sequence(elementType: .unknown) - } - if ["filter", "sorted"].contains(funcName) { - return baseType - } - } - } else { - // Base is omitted when the type can be inferred. - // For eg, we can say: let s: String = .init(...) - return .unknown - } - - } - - return .unknown - } -} - -// MARK: - TypeDecl resolve -extension GraphImpl { - - func resolveTypeDecl(tokens: [TokenSyntax]) -> TypeDecl? { - guard tokens.count > 0 else { - return nil - } - - return _resolveTypeDecl(token: tokens[0], onResult: { typeDecl in - var currentScope = typeDecl.scope - for token in tokens[1...] { - if let scope = currentScope.getTypeDecl(name: token.text).first?.scope { - currentScope = scope - } else { - return false - } - } - return true - }) - } - - private func _resolveTypeDecl(token: TokenSyntax, onResult: (TypeDecl) -> Bool) -> TypeDecl? { - let result = _findSymbol(.token(token), options: [.typeDecl]) { resolve in - if case let .typeDecl(typeDecl) = resolve { - return onResult(typeDecl) - } - return false - } - - if let result = result, case let .typeDecl(scope) = result { - return scope - } - - return nil - } - - func getAllRelatedTypeDecls(from: TypeDecl) -> [TypeDecl] { - var result: [TypeDecl] = _getAllExtensions(typeDecl: from) - if !from.isExtension { - result = [from] + result - } else { - if let originalDecl = resolveTypeDecl(tokens: from.tokens) { - result = [originalDecl] + result - } - } - - return result + result.flatMap { typeDecl -> [TypeDecl] in - guard let inheritanceTypes = typeDecl.inheritanceTypes else { - return [] - } - - return inheritanceTypes - .compactMap { resolveTypeDecl(tokens: $0.typeName.tokens ?? []) } - .flatMap { getAllRelatedTypeDecls(from: $0) } - } - } - - func getAllRelatedTypeDecls(from: TypeResolve) -> [TypeDecl] { - switch from.wrappedType { - case .type(let typeDecl): - return getAllRelatedTypeDecls(from: typeDecl) - case .sequence: - return _getAllExtensions(name: ["Array"]) + _getAllExtensions(name: ["Collection"]) - case .dict: - return _getAllExtensions(name: ["Dictionary"]) + _getAllExtensions(name: ["Collection"]) - case .name, .tuple, .unknown: - return [] - case .optional: - // Can't happen - return [] - } - } - - private func _getAllExtensions(typeDecl: TypeDecl) -> [TypeDecl] { - guard let name = _getTypeDeclFullPath(typeDecl)?.map({ $0.text }) else { return [] } - return _getAllExtensions(name: name) - } - - private func _getAllExtensions(name: [String]) -> [TypeDecl] { - return sourceFileScope.childScopes - .compactMap { $0.typeDecl } - .filter { $0.isExtension && $0.name == name } - } - - // For eg, type path for C in be example below is A.B.C - // class A { - // struct B { - // enum C { - // Returns nil if the type is nested inside non-type entity like function - private func _getTypeDeclFullPath(_ typeDecl: TypeDecl) -> [TokenSyntax]? { - let tokens = typeDecl.tokens - if typeDecl.scope.parent?.type == .sourceFileNode { - return tokens - } - if let parentTypeDecl = typeDecl.scope.parent?.typeDecl, let parentTokens = _getTypeDeclFullPath(parentTypeDecl) { - return parentTokens + tokens - } - return nil - } -} - -// MARK: - Classification -extension GraphImpl { - func isClosureEscape(_ closure: ClosureExprSyntax, nonEscapeRules: [NonEscapeRule]) -> Bool { - func _isClosureEscape(_ expr: ExprSyntax, isFuncParam: Bool) -> Bool { - // check cache - if let closureNode = expr.as(ClosureExprSyntax.self), let cachedResult = cachedClosureEscapeCheck[closureNode] { - return cachedResult - } - - // If it's a param, and it's inside an escaping closure, then it's also escaping - // For eg: - // func doSmth(block: @escaping () -> Void) { - // someObject.callBlock { - // block() - // } - // } - // Here block is a param and it's used inside an escaping closure - if isFuncParam { - if let parentClosure = expr.getEnclosingClosureNode() { - if isClosureEscape(parentClosure, nonEscapeRules: nonEscapeRules) { - return true - } - } - } - - // Function call expression: {...}() - if expr.isCalledExpr() { - return false // Not escape - } - - // let x = closure - // `x` may be used anywhere - if let variable = enclosingScope(for: expr._syntaxNode).getVariableBindingTo(expr: expr) { - let references = getVariableReferences(variable: variable) - for reference in references { - if _isClosureEscape(ExprSyntax(reference), isFuncParam: isFuncParam) == true { - return true // Escape - } - } - } - - // Used as argument in function call: doSmth(a, b, c: {...}) or doSmth(a, b) {...} - if let (functionCall, argument) = expr.getEnclosingFunctionCallExpression() { - if let (function, matchedInfo) = resolveFunction(functionCall) { - let param: FunctionParameterSyntax! - if let argument = argument { - param = matchedInfo.argumentToParamMapping[argument] - } else { - param = matchedInfo.trailingClosureArgumentToParam - } - guard param != nil else { fatalError("Something wrong") } - - // If the param is marked as `@escaping`, we still need to check with the non-escaping rules - // If the param is not marked as `@escaping`, and it's optional, we don't know anything about it - // If the param is not marked as `@escaping`, and it's not optional, we know it's non-escaping for sure - if !param.isEscaping && param.type?.isOptional != true { - return false - } - - // get the `.function` scope where we define this func - let scope = self.scope(for: function._syntaxNode) - assert(scope.type.isFunction) - - guard let variableForParam = scope.variables.first(where: { $0.raw.token == (param.secondName ?? param.firstName) }) else { - fatalError("Can't find the Variable that wrap the param") - } - let references = getVariableReferences(variable: variableForParam) - for referennce in references { - if _isClosureEscape(ExprSyntax(referennce), isFuncParam: true) == true { - return true - } - } - return false - } else { - // Can't resolve the function - // Use custom rules - for rule in nonEscapeRules { - if rule.isNonEscape(closureNode: expr, graph: self) { - return false - } - } - - // Still can't figure out using custom rules, assume closure is escaping - return true - } - } - - return false // It's unlikely the closure is escaping - } - - let result = _isClosureEscape(ExprSyntax(closure), isFuncParam: false) - cachedClosureEscapeCheck[closure] = result - return result - } - - func isCollection(_ node: ExprSyntax) -> Bool { - let type = resolveExprType(node) - return _isCollection(type) - } - - private func _isCollection(_ type: TypeResolve) -> Bool { - let isCollectionTypeName: ([String]) -> Bool = { (name: [String]) in - return name == ["Array"] || name == ["Dictionary"] || name == ["Set"] - } - - switch type { - case .tuple, - .unknown: - return false - case .sequence, - .dict: - return true - case .optional(let base): - return _isCollection(base) - case .name(let name): - return isCollectionTypeName(name) - case .type(let typeDecl): - let allTypeDecls = getAllRelatedTypeDecls(from: typeDecl) - for typeDecl in allTypeDecls { - if isCollectionTypeName(typeDecl.name) { - return true - } - - for inherritedName in (typeDecl.inheritanceTypes?.map { $0.typeName.name ?? [""] } ?? []) { - // If it extends any of the collection types or implements Collection protocol - if isCollectionTypeName(inherritedName) || inherritedName == ["Collection"] { - return true - } - } - } - - return false - } - } -} - -private extension Scope { - func getVariableBindingTo(expr: ExprSyntax) -> Variable? { - return variables.first(where: { variable -> Bool in - switch variable.raw { - case .param, .capture: return false - case let .binding(_, valueNode): - return valueNode != nil ? valueNode! == expr : false - } - }) - } -} - -private extension TypeResolve { - var toNilIfUnknown: TypeResolve? { - switch self { - case .unknown: return nil - default: return self - } - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/GraphBuilder.swift b/MemoryLeak/Sources/SwiftLeakCheck/GraphBuilder.swift deleted file mode 100644 index 2a91ff1..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/GraphBuilder.swift +++ /dev/null @@ -1,199 +0,0 @@ -// -// GraphBuilder.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 29/10/2019. -// - -import SwiftSyntax - -final class GraphBuilder { - static func buildGraph(node: SourceFileSyntax) -> GraphImpl { - // First round: build the graph - let visitor = GraphBuilderVistor() - visitor.walk(node) - - let graph = GraphImpl(sourceFileScope: visitor.sourceFileScope) - - // Second round: resolve the references - ReferenceBuilderVisitor(graph: graph).walk(node) - - return graph - } -} - -class BaseGraphVistor: SyntaxAnyVisitor { - override func visit(_ node: MissingDeclSyntax) -> SyntaxVisitorContinueKind { - return .skipChildren - } - - override func visit(_ node: MissingExprSyntax) -> SyntaxVisitorContinueKind { - return .skipChildren - } - - override func visit(_ node: MissingStmtSyntax) -> SyntaxVisitorContinueKind { - return .skipChildren - } - - override func visit(_ node: MissingTypeSyntax) -> SyntaxVisitorContinueKind { - return .skipChildren - } - - override func visit(_ node: MissingPatternSyntax) -> SyntaxVisitorContinueKind { - return .skipChildren - } -} - -fileprivate final class GraphBuilderVistor: BaseGraphVistor { - fileprivate var sourceFileScope: SourceFileScope! - private var stack = Stack() - - override func visitAny(_ node: Syntax) -> SyntaxVisitorContinueKind { - if let scopeNode = ScopeNode.from(node: node) { - if case let .sourceFileNode(node) = scopeNode { - assert(stack.peek() == nil) - sourceFileScope = SourceFileScope(node: node, parent: stack.peek()) - stack.push(sourceFileScope) - } else { - let scope = Scope(scopeNode: scopeNode, parent: stack.peek()) - stack.push(scope) - } - } - - /* - #if DEBUG - if node.is(ElseBlockSyntax.self) || node.is(ElseIfContinuationSyntax.self) { - assertionFailure("Unhandled case") - } - #endif - */ - - return super.visitAny(node) - } - - override func visitAnyPost(_ node: Syntax) { - if let scopeNode = ScopeNode.from(node: node) { - assert(stack.peek()?.scopeNode == scopeNode) - stack.pop() - } - super.visitAnyPost(node) - } - - // Note: this is not necessarily in a func x(param...) - // Take this example: - // x.block { param in ... } - // Swift treats `param` as ClosureParamSyntax , but if we put `param` in open and close parathenses, - // Swift will treat it as FunctionParameterSyntax - override func visit(_ node: FunctionParameterSyntax) -> SyntaxVisitorContinueKind { - - _ = super.visit(node) - - guard let scope = stack.peek(), scope.type.isFunction || scope.type == .enumCaseNode else { - fatalError() - } - guard let name = node.secondName ?? node.firstName else { - assert(scope.type == .enumCaseNode) - return .visitChildren - } - - guard name.tokenKind != .wildcardKeyword else { - return .visitChildren - } - - scope.addVariable(Variable.from(node, scope: scope)) - return .visitChildren - } - - override func visit(_ node: ClosureCaptureItemSyntax) -> SyntaxVisitorContinueKind { - - _ = super.visit(node) - - guard let scope = stack.peek(), scope.isClosure else { - fatalError() - } - - Variable.from(node, scope: scope).flatMap { scope.addVariable($0) } - return .visitChildren - } - - override func visit(_ node: ClosureParamSyntax) -> SyntaxVisitorContinueKind { - - _ = super.visit(node) - - guard let scope = stack.peek(), scope.isClosure else { - fatalError("A closure should be found for a ClosureParam node. Stack may have been corrupted") - } - scope.addVariable(Variable.from(node, scope: scope)) - return .visitChildren - } - - override func visit(_ node: PatternBindingSyntax) -> SyntaxVisitorContinueKind { - - _ = super.visit(node) - - guard let scope = stack.peek() else { - fatalError() - } - - Variable.from(node, scope: scope).forEach { - scope.addVariable($0) - } - - return .visitChildren - } - - override func visit(_ node: OptionalBindingConditionSyntax) -> SyntaxVisitorContinueKind { - - _ = super.visit(node) - - guard let scope = stack.peek() else { - fatalError() - } - - let isGuardCondition = node.isGuardCondition() - assert(!isGuardCondition || scope.type == .guardNode) - let scopeThatOwnVariable = (isGuardCondition ? scope.parent! : scope) - if let variable = Variable.from(node, scope: scopeThatOwnVariable) { - scopeThatOwnVariable.addVariable(variable) - } - return .visitChildren - } - - override func visit(_ node: ForInStmtSyntax) -> SyntaxVisitorContinueKind { - - _ = super.visit(node) - - guard let scope = stack.peek(), scope.type == .forLoopNode else { - fatalError() - } - - Variable.from(node, scope: scope).forEach { variable in - scope.addVariable(variable) - } - - return .visitChildren - } -} - -/// Visit the tree and resolve references -private final class ReferenceBuilderVisitor: BaseGraphVistor { - private let graph: GraphImpl - init(graph: GraphImpl) { - self.graph = graph - super.init(viewMode: .sourceAccurate) - } - - override func visit(_ node: IdentifierExprSyntax) -> SyntaxVisitorContinueKind { - graph.resolveVariable(node) - return .visitChildren - } -} - -private extension Scope { - var isClosure: Bool { - return type == .closureNode - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/GraphLeakDetector.swift b/MemoryLeak/Sources/SwiftLeakCheck/GraphLeakDetector.swift deleted file mode 100644 index 3e94510..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/GraphLeakDetector.swift +++ /dev/null @@ -1,109 +0,0 @@ -// -// GraphLeakDetector.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 12/11/2019. -// - -import SwiftSyntax - -public final class GraphLeakDetector: BaseSyntaxTreeLeakDetector { - - public var nonEscapeRules: [NonEscapeRule] = [] - - override public func detect(_ sourceFileNode: SourceFileSyntax) -> [Leak] { - var res: [Leak] = [] - let graph = GraphBuilder.buildGraph(node: sourceFileNode) - let sourceLocationConverter = SourceLocationConverter(file: "", tree: sourceFileNode) - let visitor = LeakSyntaxVisitor(graph: graph, nonEscapeRules: nonEscapeRules, sourceLocationConverter: sourceLocationConverter) { leak in - res.append(leak) - } - visitor.walk(sourceFileNode) - return res - } -} - -private final class LeakSyntaxVisitor: BaseGraphVistor { - private let graph: GraphImpl - private let sourceLocationConverter: SourceLocationConverter - private let onLeakDetected: (Leak) -> Void - private let nonEscapeRules: [NonEscapeRule] - - init(graph: GraphImpl, - nonEscapeRules: [NonEscapeRule], - sourceLocationConverter: SourceLocationConverter, - onLeakDetected: @escaping (Leak) -> Void) { - self.graph = graph - self.sourceLocationConverter = sourceLocationConverter - self.nonEscapeRules = nonEscapeRules - self.onLeakDetected = onLeakDetected - super.init(viewMode: .sourceAccurate) - } - - override func visit(_ node: IdentifierExprSyntax) -> SyntaxVisitorContinueKind { - detectLeak(node) - return .skipChildren - } - - private func detectLeak(_ node: IdentifierExprSyntax) { - var leak: Leak? - defer { - if let leak = leak { - onLeakDetected(leak) - } - } - - if node.getEnclosingClosureNode() == nil { - // Not inside closure -> ignore - return - } - - if !graph.couldReferenceSelf(ExprSyntax(node)) { - return - } - - var currentScope: Scope! = graph.closetScopeThatCanResolveSymbol(.identifier(node)) - var isEscape = false - while currentScope != nil { - if let variable = currentScope.getVariable(node) { - if !isEscape { - // No leak - return - } - - switch variable.raw { - case .param: - fatalError("Can't happen since a param cannot reference `self`") - case let .capture(capturedNode): - if variable.isStrong && isEscape { - leak = Leak(node: node, capturedNode: ExprSyntax(capturedNode), sourceLocationConverter: sourceLocationConverter) - } - case let .binding(_, valueNode): - if let referenceNode = valueNode?.as(IdentifierExprSyntax.self) { - if variable.isStrong && isEscape { - leak = Leak(node: node, capturedNode: ExprSyntax(referenceNode), sourceLocationConverter: sourceLocationConverter) - } - } else { - fatalError("Can't reference `self`") - } - } - - return - } - - if case let .closureNode(closureNode) = currentScope.scopeNode { - isEscape = graph.isClosureEscape(closureNode, nonEscapeRules: nonEscapeRules) - } - - currentScope = currentScope.parent - } - - if isEscape { - leak = Leak(node: node, capturedNode: nil, sourceLocationConverter: sourceLocationConverter) - return - } - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/Leak.swift b/MemoryLeak/Sources/SwiftLeakCheck/Leak.swift deleted file mode 100644 index 3983207..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/Leak.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// LeakDetection.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 27/10/2019. -// - -import Foundation -import SwiftSyntax - -open class Leak: CustomStringConvertible, Encodable { - public let node: IdentifierExprSyntax - public let capturedNode: ExprSyntax? - public let sourceLocationConverter: SourceLocationConverter - - public private(set) lazy var line: Int = { - return sourceLocationConverter.location(for: node.positionAfterSkippingLeadingTrivia).line ?? -1 - }() - - public private(set) lazy var column: Int = { - return sourceLocationConverter.location(for: node.positionAfterSkippingLeadingTrivia).column ?? -1 - }() - - public init(node: IdentifierExprSyntax, - capturedNode: ExprSyntax?, - sourceLocationConverter: SourceLocationConverter) { - self.node = node - self.capturedNode = capturedNode - self.sourceLocationConverter = sourceLocationConverter - } - - private enum CodingKeys: CodingKey { - case line - case column - case reason - } - - open func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(line, forKey: .line) - try container.encode(column, forKey: .column) - - let reason: String = { - return "`self` is strongly captured here, from a potentially escaped closure." - }() - try container.encode(reason, forKey: .reason) - } - - open var description: String { - return """ - `self` is strongly captured at (line: \(line), column: \(column))"), - from a potentially escaped closure. - """ - } - - open func xcodeWarning(path: String) ->String { - return "\(path):\(line):\(column): warning: `self` is strongly captured" - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/LeakDetector.swift b/MemoryLeak/Sources/SwiftLeakCheck/LeakDetector.swift deleted file mode 100644 index 703d8a3..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/LeakDetector.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// LeakDetector.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 27/10/2019. -// - -import SwiftSyntax -import Foundation - -public protocol LeakDetector { - func detect(content: String) throws -> [Leak] -} - -extension LeakDetector { - public func detect(_ filePath: String) throws -> [Leak] { - return try detect(content: String(contentsOfFile: filePath)) - } - - public func detect(_ url: URL) throws -> [Leak] { - return try detect(content: String(contentsOf: url)) - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/CollectionRules.swift b/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/CollectionRules.swift deleted file mode 100644 index 4ae2109..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/CollectionRules.swift +++ /dev/null @@ -1,181 +0,0 @@ -// -// CollectionRules.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 29/10/2019. -// - -import SwiftSyntax - -/// Swift Collection functions like forEach, map, flatMap, sorted,.... -public enum CollectionRules { - - public private(set) static var rules: [NonEscapeRule] = { - return [ - CollectionForEachRule(), - CollectionCompactMapRule(), - CollectionMapRule(), - CollectionFilterRule(), - CollectionSortRule(), - CollectionFlatMapRule(), - CollectionFirstWhereRule(), - CollectionContainsRule(), - CollectionMaxMinRule() - ] - }() -} - -open class CollectionForEachRule: BaseNonEscapeRule { - public let mustBeCollection: Bool - private let signature = FunctionSignature(name: "forEach", params: [ - FunctionParam(name: nil, isClosure: true) - ]) - public init(mustBeCollection: Bool = false) { - self.mustBeCollection = mustBeCollection - } - - open override func isNonEscape(arg: FunctionCallArgumentSyntax?, - funcCallExpr: FunctionCallExprSyntax, - graph: Graph) -> Bool { - return funcCallExpr.match(.funcCall(signature: signature, base: .init { expr in - return !self.mustBeCollection || isCollection(expr, graph: graph) - })) - } -} - -open class CollectionCompactMapRule: BaseNonEscapeRule { - private let signature = FunctionSignature(name: "compactMap", params: [ - FunctionParam(name: nil, isClosure: true) - ]) - - open override func isNonEscape(arg: FunctionCallArgumentSyntax?, - funcCallExpr: FunctionCallExprSyntax, - graph: Graph) -> Bool { - return funcCallExpr.match(.funcCall(signature: signature, base: .init { expr in - return isCollection(expr, graph: graph) - })) - } -} - -open class CollectionMapRule: BaseNonEscapeRule { - private let signature = FunctionSignature(name: "map", params: [ - FunctionParam(name: nil, isClosure: true) - ]) - - open override func isNonEscape(arg: FunctionCallArgumentSyntax?, - funcCallExpr: FunctionCallExprSyntax, - graph: Graph) -> Bool { - return funcCallExpr.match(.funcCall(signature: signature, base: .init { expr in - return isCollection(expr, graph: graph) || isOptional(expr, graph: graph) - })) - } -} - -open class CollectionFlatMapRule: BaseNonEscapeRule { - private let signature = FunctionSignature(name: "flatMap", params: [ - FunctionParam(name: nil, isClosure: true) - ]) - - open override func isNonEscape(arg: FunctionCallArgumentSyntax?, - funcCallExpr: FunctionCallExprSyntax, - graph: Graph) -> Bool { - return funcCallExpr.match(.funcCall(signature: signature, base: .init { expr in - return isCollection(expr, graph: graph) || isOptional(expr, graph: graph) - })) - } -} - -open class CollectionFilterRule: BaseNonEscapeRule { - private let signature = FunctionSignature(name: "filter", params: [ - FunctionParam(name: nil, isClosure: true) - ]) - - open override func isNonEscape(arg: FunctionCallArgumentSyntax?, - funcCallExpr: FunctionCallExprSyntax, - graph: Graph) -> Bool { - return funcCallExpr.match(.funcCall(signature: signature, base: .init { expr in - return isCollection(expr, graph: graph) - })) - } -} - -open class CollectionSortRule: BaseNonEscapeRule { - private let sortSignature = FunctionSignature(name: "sort", params: [ - FunctionParam(name: "by", isClosure: true) - ]) - private let sortedSignature = FunctionSignature(name: "sorted", params: [ - FunctionParam(name: "by", isClosure: true) - ]) - - open override func isNonEscape(arg: FunctionCallArgumentSyntax?, - funcCallExpr: FunctionCallExprSyntax, - graph: Graph) -> Bool { - return funcCallExpr.match(.funcCall(signature: sortSignature, base: .init { return isCollection($0, graph: graph) })) - || funcCallExpr.match(.funcCall(signature: sortedSignature, base: .init { return isCollection($0, graph: graph) })) - } -} - -open class CollectionFirstWhereRule: BaseNonEscapeRule { - private let firstWhereSignature = FunctionSignature(name: "first", params: [ - FunctionParam(name: "where", isClosure: true) - ]) - private let firstIndexWhereSignature = FunctionSignature(name: "firstIndex", params: [ - FunctionParam(name: "where", isClosure: true) - ]) - - open override func isNonEscape(arg: FunctionCallArgumentSyntax?, - funcCallExpr: FunctionCallExprSyntax, - graph: Graph) -> Bool { - let base = ExprSyntaxPredicate { expr in - return isCollection(expr, graph: graph) - } - return funcCallExpr.match(.funcCall(signature: firstWhereSignature, base: base)) - || funcCallExpr.match(.funcCall(signature: firstIndexWhereSignature, base: base)) - } -} - -open class CollectionContainsRule: BaseNonEscapeRule { - let signature = FunctionSignature(name: "contains", params: [ - FunctionParam(name: "where", isClosure: true) - ]) - - open override func isNonEscape(arg: FunctionCallArgumentSyntax?, - funcCallExpr: FunctionCallExprSyntax, - graph: Graph) -> Bool { - return funcCallExpr.match(.funcCall(signature: signature, base: .init { expr in - return isCollection(expr, graph: graph) })) - } -} - -open class CollectionMaxMinRule: BaseNonEscapeRule { - private let maxSignature = FunctionSignature(name: "max", params: [ - FunctionParam(name: "by", isClosure: true) - ]) - private let minSignature = FunctionSignature(name: "min", params: [ - FunctionParam(name: "by", isClosure: true) - ]) - - open override func isNonEscape(arg: FunctionCallArgumentSyntax?, - funcCallExpr: FunctionCallExprSyntax, - graph: Graph) -> Bool { - return funcCallExpr.match(.funcCall(signature: maxSignature, base: .init { return isCollection($0, graph: graph) })) - || funcCallExpr.match(.funcCall(signature: minSignature, base: .init { return isCollection($0, graph: graph) })) - } -} - -private func isCollection(_ expr: ExprSyntax?, graph: Graph) -> Bool { - guard let expr = expr else { - return false - } - return graph.isCollection(expr) -} - -private func isOptional(_ expr: ExprSyntax?, graph: Graph) -> Bool { - guard let expr = expr else { - return false - } - return graph.resolveExprType(expr).isOptional -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/DispatchQueueRule.swift b/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/DispatchQueueRule.swift deleted file mode 100644 index 3bdd59f..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/DispatchQueueRule.swift +++ /dev/null @@ -1,118 +0,0 @@ -// -// DispatchQueueRule.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 28/10/2019. -// - -import SwiftSyntax - -open class DispatchQueueRule: BaseNonEscapeRule { - - private let signatures: [FunctionSignature] = [ - FunctionSignature(name: "async", params: [ - FunctionParam(name: "group", canOmit: true), - FunctionParam(name: "qos", canOmit: true), - FunctionParam(name: "flags", canOmit: true), - FunctionParam(name: "execute", isClosure: true) - ]), - FunctionSignature(name: "async", params: [ - FunctionParam(name: "group", canOmit: true), - FunctionParam(name: "execute") - ]), - FunctionSignature(name: "sync", params: [ - FunctionParam(name: "flags", canOmit: true), - FunctionParam(name: "execute", isClosure: true) - ]), - FunctionSignature(name: "sync", params: [ - FunctionParam(name: "execute") - ]), - FunctionSignature(name: "asyncAfter", params: [ - FunctionParam(name: "deadline"), - FunctionParam(name: "qos", canOmit: true), - FunctionParam(name: "flags", canOmit: true), - FunctionParam(name: "execute", isClosure: true) - ]), - FunctionSignature(name: "asyncAfter", params: [ - FunctionParam(name: "wallDeadline"), - FunctionParam(name: "qos", canOmit: true), - FunctionParam(name: "flags", canOmit: true), - FunctionParam(name: "execute", isClosure: true) - ]) - ] - - private let mainQueuePredicate: ExprSyntaxPredicate = .memberAccess("main", base: .name("DispatchQueue")) - private let globalQueuePredicate: ExprSyntaxPredicate = .funcCall( - signature: FunctionSignature(name: "global", params: [.init(name: "qos", canOmit: true)]), - base: .name("DispatchQueue") - ) - - - open override func isNonEscape(arg: FunctionCallArgumentSyntax?, - funcCallExpr: FunctionCallExprSyntax, - graph: Graph) -> Bool { - - for signature in signatures { - for queue in [mainQueuePredicate, globalQueuePredicate] { - let predicate: ExprSyntaxPredicate = .funcCall(signature: signature, base: queue) - if funcCallExpr.match(predicate) { - return true - } - } - } - - let isDispatchQueuePredicate: ExprSyntaxPredicate = .init { expr -> Bool in - guard let expr = expr else { return false } - let typeResolve = graph.resolveExprType(expr) - switch typeResolve.wrappedType { - case .name(let name): - return self.isDispatchQueueType(name: name) - case .type(let typeDecl): - let allTypeDecls = graph.getAllRelatedTypeDecls(from: typeDecl) - for typeDecl in allTypeDecls { - if self.isDispatchQueueType(typeDecl: typeDecl) { - return true - } - } - - return false - - case .dict, - .sequence, - .tuple, - .optional, // Can't happen - .unknown: - return false - } - } - - for signature in signatures { - let predicate: ExprSyntaxPredicate = .funcCall(signature: signature, base: isDispatchQueuePredicate) - if funcCallExpr.match(predicate) { - return true - } - } - - return false - } - - private func isDispatchQueueType(name: [String]) -> Bool { - return name == ["DispatchQueue"] - } - - private func isDispatchQueueType(typeDecl: TypeDecl) -> Bool { - if self.isDispatchQueueType(name: typeDecl.name) { - return true - } - for inheritedType in (typeDecl.inheritanceTypes ?? []) { - if self.isDispatchQueueType(name: inheritedType.typeName.name ?? []) { - return true - } - } - return false - } -} - diff --git a/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/ExprSyntaxPredicate.swift b/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/ExprSyntaxPredicate.swift deleted file mode 100644 index 3a039e4..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/ExprSyntaxPredicate.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// ExprSyntaxPredicate.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 26/12/2019. -// - -import SwiftSyntax - -open class ExprSyntaxPredicate { - public let match: (ExprSyntax?) -> Bool - public init(_ match: @escaping (ExprSyntax?) -> Bool) { - self.match = match - } - - public static let any: ExprSyntaxPredicate = .init { _ in true } -} - -// MARK: - Identifier predicate -extension ExprSyntaxPredicate { - public static func name(_ text: String) -> ExprSyntaxPredicate { - return .name({ $0 == text }) - } - - public static func name(_ namePredicate: @escaping (String) -> Bool) -> ExprSyntaxPredicate { - return .init({ expr -> Bool in - guard let identifierExpr = expr?.as(IdentifierExprSyntax.self) else { - return false - } - return namePredicate(identifierExpr.identifier.text) - }) - } -} - -// MARK: - Function call predicate -extension ExprSyntaxPredicate { - public static func funcCall(name: String, - base basePredicate: ExprSyntaxPredicate) -> ExprSyntaxPredicate { - return .funcCall(namePredicate: { $0 == name }, base: basePredicate) - } - - public static func funcCall(namePredicate: @escaping (String) -> Bool, - base basePredicate: ExprSyntaxPredicate) -> ExprSyntaxPredicate { - return .funcCall(predicate: { funcCallExpr -> Bool in - guard let symbol = funcCallExpr.symbol else { - return false - } - return namePredicate(symbol.text) - && basePredicate.match(funcCallExpr.base) - }) - } - - public static func funcCall(signature: FunctionSignature, - base basePredicate: ExprSyntaxPredicate) -> ExprSyntaxPredicate { - return .funcCall(predicate: { funcCallExpr -> Bool in - return signature.match(funcCallExpr).isMatched - && basePredicate.match(funcCallExpr.base) - }) - } - - public static func funcCall(predicate: @escaping (FunctionCallExprSyntax) -> Bool) -> ExprSyntaxPredicate { - return .init({ expr -> Bool in - guard let funcCallExpr = expr?.as(FunctionCallExprSyntax.self) else { - return false - } - return predicate(funcCallExpr) - }) - } -} - -// MARK: - MemberAccess predicate -extension ExprSyntaxPredicate { - public static func memberAccess(_ memberPredicate: @escaping (String) -> Bool, - base basePredicate: ExprSyntaxPredicate) -> ExprSyntaxPredicate { - return .init({ expr -> Bool in - guard let memberAccessExpr = expr?.as(MemberAccessExprSyntax.self) else { - return false - } - return memberPredicate(memberAccessExpr.name.text) - && basePredicate.match(memberAccessExpr.base) - }) - } - - public static func memberAccess(_ member: String, base basePredicate: ExprSyntaxPredicate) -> ExprSyntaxPredicate { - return .memberAccess({ $0 == member }, base: basePredicate) - } -} - -public extension ExprSyntax { - - func match(_ predicate: ExprSyntaxPredicate) -> Bool { - return predicate.match(self) - } -} - -// Convenient -public extension FunctionCallExprSyntax { - func match(_ predicate: ExprSyntaxPredicate) -> Bool { - return predicate.match(ExprSyntax(self)) - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/NonEscapeRule.swift b/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/NonEscapeRule.swift deleted file mode 100644 index 4de2b90..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/NonEscapeRule.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// NonEscapeRule.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 28/10/2019. -// - -import SwiftSyntax - -public protocol NonEscapeRule { - func isNonEscape(closureNode: ExprSyntax, graph: Graph) -> Bool -} - -open class BaseNonEscapeRule: NonEscapeRule { - public init() {} - - public func isNonEscape(closureNode: ExprSyntax, graph: Graph) -> Bool { - guard let (funcCallExpr, arg) = closureNode.getEnclosingFunctionCallExpression() else { - return false - } - - return isNonEscape( - arg: arg, - funcCallExpr: funcCallExpr, - graph: graph - ) - } - - /// Returns whether a given argument is escaping in a function call - /// - /// - Parameters: - /// - arg: The closure argument, or nil if it's trailing closure - /// - funcCallExpr: the source FunctionCallExprSyntax - /// - graph: Source code graph. Use it to retrieve more info - /// - Returns: true if the closure is non-escaping, false otherwise - open func isNonEscape(arg: FunctionCallArgumentSyntax?, - funcCallExpr: FunctionCallExprSyntax, - graph: Graph) -> Bool { - return false - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/UIViewAnimationRule.swift b/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/UIViewAnimationRule.swift deleted file mode 100644 index 005dda7..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/UIViewAnimationRule.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// UIViewAnimationRule.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 28/10/2019. -// - -import SwiftSyntax - -/// Eg, UIView.animate(..., animations: {...}) { -/// ..... -/// } -open class UIViewAnimationRule: BaseNonEscapeRule { - - private let signatures: [FunctionSignature] = [ - FunctionSignature(name: "animate", params: [ - FunctionParam(name: "withDuration"), - FunctionParam(name: "animations", isClosure: true) - ]), - FunctionSignature(name: "animate", params: [ - FunctionParam(name: "withDuration"), - FunctionParam(name: "animations", isClosure: true), - FunctionParam(name: "completion", isClosure: true, canOmit: true) - ]), - FunctionSignature(name: "animate", params: [ - FunctionParam(name: "withDuration"), - FunctionParam(name: "delay"), - FunctionParam(name: "options", canOmit: true), - FunctionParam(name: "animations", isClosure: true), - FunctionParam(name: "completion", isClosure: true, canOmit: true) - ]), - FunctionSignature(name: "animate", params: [ - FunctionParam(name: "withDuration"), - FunctionParam(name: "delay"), - FunctionParam(name: "usingSpringWithDamping"), - FunctionParam(name: "initialSpringVelocity"), - FunctionParam(name: "options", canOmit: true), - FunctionParam(name: "animations", isClosure: true), - FunctionParam(name: "completion", isClosure: true, canOmit: true) - ]), - FunctionSignature(name: "transition", params: [ - FunctionParam(name: "from"), - FunctionParam(name: "to"), - FunctionParam(name: "duration"), - FunctionParam(name: "options"), - FunctionParam(name: "completion", isClosure: true, canOmit: true), - ]), - FunctionSignature( name: "transition", params: [ - FunctionParam(name: "with"), - FunctionParam(name: "duration"), - FunctionParam(name: "options"), - FunctionParam(name: "animations", isClosure: true, canOmit: true), - FunctionParam(name: "completion", isClosure: true, canOmit: true), - ]), - FunctionSignature(name: "animateKeyframes", params: [ - FunctionParam(name: "withDuration"), - FunctionParam(name: "delay", canOmit: true), - FunctionParam(name: "options", canOmit: true), - FunctionParam(name: "animations", isClosure: true), - FunctionParam(name: "completion", isClosure: true) - ]) - ] - - open override func isNonEscape(arg: FunctionCallArgumentSyntax?, - funcCallExpr: FunctionCallExprSyntax, - graph: Graph) -> Bool { - - // Check if base is `UIView`, if not we can end early without checking any of the signatures - guard funcCallExpr.match(.funcCall(namePredicate: { _ in true }, base: .name("UIView"))) else { - return false - } - - // Now we can check each signature and ignore the base (already checked) - for signature in signatures { - if funcCallExpr.match(.funcCall(signature: signature, base: .any)) { - return true - } - } - - return false - } -} - diff --git a/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/UIViewControllerAnimationRule.swift b/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/UIViewControllerAnimationRule.swift deleted file mode 100644 index 0320aa7..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/NonEscapeRules/UIViewControllerAnimationRule.swift +++ /dev/null @@ -1,128 +0,0 @@ -// -// UIViewControllerAnimationRule.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 30/12/2019. -// - -import SwiftSyntax - -/// Eg, someViewController.present(vc, animated: true, completion: { ... }) -/// or someViewController.dismiss(animated: true) { ... } -open class UIViewControllerAnimationRule: BaseNonEscapeRule { - - private let signatures: [FunctionSignature] = [ - FunctionSignature(name: "present", params: [ - FunctionParam(name: nil), // "viewControllerToPresent" - FunctionParam(name: "animated"), - FunctionParam(name: "completion", isClosure: true, canOmit: true) - ]), - FunctionSignature(name: "dismiss", params: [ - FunctionParam(name: "animated"), - FunctionParam(name: "completion", isClosure: true, canOmit: true) - ]), - FunctionSignature(name: "transition", params: [ - FunctionParam(name: "from"), - FunctionParam(name: "to"), - FunctionParam(name: "duration"), - FunctionParam(name: "options", canOmit: true), - FunctionParam(name: "animations", isClosure: true), - FunctionParam(name: "completion", isClosure: true, canOmit: true) - ]) - ] - - open override func isNonEscape(arg: FunctionCallArgumentSyntax?, - funcCallExpr: FunctionCallExprSyntax, - graph: Graph) -> Bool { - - // Make sure the func is called from UIViewController - guard isCalledFromUIViewController(funcCallExpr: funcCallExpr, graph: graph) else { - return false - } - - // Now we can check each signature and ignore the base that is already checked - for signature in signatures { - if funcCallExpr.match(.funcCall(signature: signature, base: .any)) { - return true - } - } - - return false - } - - open func isUIViewControllerType(name: [String]) -> Bool { - - let typeName = name.last ?? "" - - let candidates = [ - "UIViewController", - "UITableViewController", - "UICollectionViewController", - "UIAlertController", - "UIActivityViewController", - "UINavigationController", - "UITabBarController", - "UIMenuController", - "UISearchController" - ] - - return candidates.contains(typeName) || typeName.hasSuffix("ViewController") - } - - private func isUIViewControllerType(typeDecl: TypeDecl) -> Bool { - if isUIViewControllerType(name: typeDecl.name) { - return true - } - - let inheritantTypes = (typeDecl.inheritanceTypes ?? []).map { $0.typeName } - for inheritantType in inheritantTypes { - if isUIViewControllerType(name: inheritantType.name ?? []) { - return true - } - } - - return false - } - - private func isCalledFromUIViewController(funcCallExpr: FunctionCallExprSyntax, graph: Graph) -> Bool { - guard let base = funcCallExpr.base else { - // No base, eg: doSmth() - // class SomeClass { - // func main() { - // doSmth() - // } - // } - // In this case, we find the TypeDecl where this func is called from (Eg, SomeClass) - if let typeDecl = graph.enclosingTypeDecl(for: funcCallExpr._syntaxNode) { - return isUIViewControllerType(typeDecl: typeDecl) - } else { - return false - } - } - - // Eg: base.doSmth() - // We check if base is UIViewController - let typeResolve = graph.resolveExprType(base) - switch typeResolve.wrappedType { - case .type(let typeDecl): - let allTypeDecls = graph.getAllRelatedTypeDecls(from: typeDecl) - for typeDecl in allTypeDecls { - if isUIViewControllerType(typeDecl: typeDecl) { - return true - } - } - return false - case .name(let name): - return isUIViewControllerType(name: name) - case .dict, - .sequence, - .tuple, - .optional, // Can't happen - .unknown: - return false - } - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/Scope.swift b/MemoryLeak/Sources/SwiftLeakCheck/Scope.swift deleted file mode 100644 index 2c41ea2..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/Scope.swift +++ /dev/null @@ -1,332 +0,0 @@ -// -// Scope.swift -// LeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 27/10/2019. -// - -import SwiftSyntax - -public enum ScopeNode: Hashable, CustomStringConvertible { - case sourceFileNode(SourceFileSyntax) - case classNode(ClassDeclSyntax) - case structNode(StructDeclSyntax) - case enumNode(EnumDeclSyntax) - case enumCaseNode(EnumCaseDeclSyntax) - case extensionNode(ExtensionDeclSyntax) - case funcNode(FunctionDeclSyntax) - case initialiseNode(InitializerDeclSyntax) - case closureNode(ClosureExprSyntax) - case ifBlockNode(CodeBlockSyntax, IfStmtSyntax) // If block in a `IfStmtSyntax` - case elseBlockNode(CodeBlockSyntax, IfStmtSyntax) // Else block in a `IfStmtSyntax` - case guardNode(GuardStmtSyntax) - case forLoopNode(ForInStmtSyntax) - case whileLoopNode(WhileStmtSyntax) - case subscriptNode(SubscriptDeclSyntax) - case accessorNode(AccessorDeclSyntax) - case variableDeclNode(CodeBlockSyntax) // var x: Int { ... } - case switchCaseNode(SwitchCaseSyntax) - - public static func from(node: Syntax) -> ScopeNode? { - if let sourceFileNode = node.as(SourceFileSyntax.self) { - return .sourceFileNode(sourceFileNode) - } - - if let classNode = node.as(ClassDeclSyntax.self) { - return .classNode(classNode) - } - - if let structNode = node.as(StructDeclSyntax.self) { - return .structNode(structNode) - } - - if let enumNode = node.as(EnumDeclSyntax.self) { - return .enumNode(enumNode) - } - - if let enumCaseNode = node.as(EnumCaseDeclSyntax.self) { - return .enumCaseNode(enumCaseNode) - } - - if let extensionNode = node.as(ExtensionDeclSyntax.self) { - return .extensionNode(extensionNode) - } - - if let funcNode = node.as(FunctionDeclSyntax.self) { - return .funcNode(funcNode) - } - - if let initialiseNode = node.as(InitializerDeclSyntax.self) { - return .initialiseNode(initialiseNode) - } - - if let closureNode = node.as(ClosureExprSyntax.self) { - return .closureNode(closureNode) - } - - if let codeBlockNode = node.as(CodeBlockSyntax.self), codeBlockNode.parent?.is(IfStmtSyntax.self) == true { - let parent = (codeBlockNode.parent?.as(IfStmtSyntax.self))! - if codeBlockNode == parent.body { - return .ifBlockNode(codeBlockNode, parent) - } else if codeBlockNode == parent.elseBody?.as(CodeBlockSyntax.self) { - return .elseBlockNode(codeBlockNode, parent) - } - return nil - } - - if let guardNode = node.as(GuardStmtSyntax.self) { - return .guardNode(guardNode) - } - - if let forLoopNode = node.as(ForInStmtSyntax.self) { - return .forLoopNode(forLoopNode) - } - - if let whileLoopNode = node.as(WhileStmtSyntax.self) { - return .whileLoopNode(whileLoopNode) - } - - if let subscriptNode = node.as(SubscriptDeclSyntax.self) { - return .subscriptNode(subscriptNode) - } - - if let accessorNode = node.as(AccessorDeclSyntax.self) { - return .accessorNode(accessorNode) - } - - if let codeBlockNode = node.as(CodeBlockSyntax.self), - codeBlockNode.parent?.is(PatternBindingSyntax.self) == true, - codeBlockNode.parent?.parent?.is(PatternBindingListSyntax.self) == true, - codeBlockNode.parent?.parent?.parent?.is(VariableDeclSyntax.self) == true { - return .variableDeclNode(codeBlockNode) - } - - if let switchCaseNode = node.as(SwitchCaseSyntax.self) { - return .switchCaseNode(switchCaseNode) - } - - return nil - } - - public var node: Syntax { - switch self { - case .sourceFileNode(let node): return node._syntaxNode - case .classNode(let node): return node._syntaxNode - case .structNode(let node): return node._syntaxNode - case .enumNode(let node): return node._syntaxNode - case .enumCaseNode(let node): return node._syntaxNode - case .extensionNode(let node): return node._syntaxNode - case .funcNode(let node): return node._syntaxNode - case .initialiseNode(let node): return node._syntaxNode - case .closureNode(let node): return node._syntaxNode - case .ifBlockNode(let node, _): return node._syntaxNode - case .elseBlockNode(let node, _): return node._syntaxNode - case .guardNode(let node): return node._syntaxNode - case .forLoopNode(let node): return node._syntaxNode - case .whileLoopNode(let node): return node._syntaxNode - case .subscriptNode(let node): return node._syntaxNode - case .accessorNode(let node): return node._syntaxNode - case .variableDeclNode(let node): return node._syntaxNode - case .switchCaseNode(let node): return node._syntaxNode - } - } - - public var type: ScopeType { - switch self { - case .sourceFileNode: return .sourceFileNode - case .classNode: return .classNode - case .structNode: return .structNode - case .enumNode: return .enumNode - case .enumCaseNode: return .enumCaseNode - case .extensionNode: return .extensionNode - case .funcNode: return .funcNode - case .initialiseNode: return .initialiseNode - case .closureNode: return .closureNode - case .ifBlockNode, .elseBlockNode: return .ifElseNode - case .guardNode: return .guardNode - case .forLoopNode: return .forLoopNode - case .whileLoopNode: return .whileLoopNode - case .subscriptNode: return .subscriptNode - case .accessorNode: return .accessorNode - case .variableDeclNode: return .variableDeclNode - case .switchCaseNode: return .switchCaseNode - } - } - - public var enclosingScopeNode: ScopeNode? { - return node.enclosingScopeNode - } - - public var description: String { - return "\(node)" - } -} - -public enum ScopeType: Equatable { - case sourceFileNode - case classNode - case structNode - case enumNode - case enumCaseNode - case extensionNode - case funcNode - case initialiseNode - case closureNode - case ifElseNode - case guardNode - case forLoopNode - case whileLoopNode - case subscriptNode - case accessorNode - case variableDeclNode - case switchCaseNode - - public var isTypeDecl: Bool { - return self == .classNode - || self == .structNode - || self == .enumNode - || self == .extensionNode - } - - public var isFunction: Bool { - return self == .funcNode - || self == .initialiseNode - || self == .closureNode - || self == .subscriptNode - } - -} - -open class Scope: Hashable, CustomStringConvertible { - public let scopeNode: ScopeNode - public let parent: Scope? - public private(set) var variables = Stack() - public private(set) var childScopes = [Scope]() - public var type: ScopeType { - return scopeNode.type - } - - public var childFunctions: [Function] { - return childScopes - .compactMap { scope in - if case let .funcNode(funcNode) = scope.scopeNode { - return funcNode - } - return nil - } - } - - public var childTypeDecls: [TypeDecl] { - return childScopes - .compactMap { $0.typeDecl } - } - - public var typeDecl: TypeDecl? { - switch scopeNode { - case .classNode(let node): - return TypeDecl(tokens: [node.identifier], inheritanceTypes: node.inheritanceClause?.inheritedTypeCollection.map { $0 }, scope: self) - case .structNode(let node): - return TypeDecl(tokens: [node.identifier], inheritanceTypes: node.inheritanceClause?.inheritedTypeCollection.map { $0 }, scope: self) - case .enumNode(let node): - return TypeDecl(tokens: [node.identifier], inheritanceTypes: node.inheritanceClause?.inheritedTypeCollection.map { $0 }, scope: self) - case .extensionNode(let node): - return TypeDecl(tokens: node.extendedType.tokens!, inheritanceTypes: node.inheritanceClause?.inheritedTypeCollection.map { $0 }, scope: self) - default: - return nil - } - } - - // Whether a variable can be used before it's declared. This is true for node that defines type, such as class, struct, enum,.... - // Otherwise if a variable is inside func, or closure, or normal block (if, guard,..), it must be declared before being used - public var canUseVariableOrFuncInAnyOrder: Bool { - return type == .classNode - || type == .structNode - || type == .enumNode - || type == .extensionNode - || type == .sourceFileNode - } - - public init(scopeNode: ScopeNode, parent: Scope?) { - self.parent = parent - self.scopeNode = scopeNode - parent?.childScopes.append(self) - - if let parent = parent { - assert(scopeNode.node.isDescendent(of: parent.scopeNode.node)) - } - } - - func addVariable(_ variable: Variable) { - assert(variable.scope == self) - variables.push(variable) - } - - func getVariable(_ node: IdentifierExprSyntax) -> Variable? { - let name = node.identifier.text - for variable in variables.filter({ $0.name == name }) { - // Special case: guard let `x` = x else { ... } - // or: let x = x.doSmth() - // Here x on the right cannot be resolved to x on the left - if case let .binding(_, valueNode) = variable.raw, - valueNode != nil && node._syntaxNode.isDescendent(of: valueNode!._syntaxNode) { - continue - } - - if variable.raw.token.isBefore(node) { - return variable - } else if !canUseVariableOrFuncInAnyOrder { - // Stop - break - } - } - - return nil - } - - func getFunctionWithSymbol(_ symbol: Symbol) -> [Function] { - return childFunctions.filter { function in - if function.identifier.isBefore(symbol.node) || canUseVariableOrFuncInAnyOrder { - return function.identifier.text == symbol.name - } - return false - } - } - - func getTypeDecl(name: String) -> [TypeDecl] { - return childTypeDecls - .filter { typeDecl in - return typeDecl.name == [name] - } - } - - open var description: String { - return "\(scopeNode)" - } -} - -// MARK: - Hashable -extension Scope { - public func hash(into hasher: inout Hasher) { - scopeNode.hash(into: &hasher) - } - - public static func == (_ lhs: Scope, _ rhs: Scope) -> Bool { - return lhs.scopeNode == rhs.scopeNode - } -} - -extension SyntaxProtocol { - public var enclosingScopeNode: ScopeNode? { - var parent = self.parent - while parent != nil { - if let scopeNode = ScopeNode.from(node: parent!) { - return scopeNode - } - parent = parent?.parent - } - return nil - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/SourceFileScope.swift b/MemoryLeak/Sources/SwiftLeakCheck/SourceFileScope.swift deleted file mode 100644 index 16d1f79..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/SourceFileScope.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// SourceFileScope.swift -// SwiftLeakCheck -// -// Created by Hoang Le Pham on 04/01/2020. -// - -import SwiftSyntax - -public class SourceFileScope: Scope { - let node: SourceFileSyntax - init(node: SourceFileSyntax, parent: Scope?) { - self.node = node - super.init(scopeNode: .sourceFileNode(node), parent: parent) - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/Stack.swift b/MemoryLeak/Sources/SwiftLeakCheck/Stack.swift deleted file mode 100644 index e77de87..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/Stack.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// Stack.swift -// LeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 27/10/2019. -// - -public struct Stack { - private var items: [T] = [] - - public init() {} - - public init(items: [T]) { - self.items = items - } - - public mutating func push(_ item: T) { - items.append(item) - } - - @discardableResult - public mutating func pop() -> T? { - if !items.isEmpty { - return items.removeLast() - } else { - return nil - } - } - - public mutating func reset() { - items.removeAll() - } - - public func peek() -> T? { - return items.last - } -} - -extension Stack: Collection { - public var startIndex: Int { - return items.startIndex - } - - public var endIndex: Int { - return items.endIndex - } - - public func index(after i: Int) -> Int { - return items.index(after: i) - } - - public subscript(_ index: Int) -> T { - return items[items.count - index - 1] - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/SwiftSyntax+Extensions.swift b/MemoryLeak/Sources/SwiftLeakCheck/SwiftSyntax+Extensions.swift deleted file mode 100644 index 7fe5234..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/SwiftSyntax+Extensions.swift +++ /dev/null @@ -1,263 +0,0 @@ -// -// SwiftSyntax+Extensions.swift -// LeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 27/10/2019. -// - -import SwiftSyntax - -public extension SyntaxProtocol { - func isBefore(_ node: SyntaxProtocol) -> Bool { - return positionAfterSkippingLeadingTrivia.utf8Offset < node.positionAfterSkippingLeadingTrivia.utf8Offset - } - - func getEnclosingNode(_ type: T.Type) -> T? { - var parent = self.parent - while parent != nil && parent!.is(type) == false { - parent = parent?.parent - if parent == nil { return nil } - } - return parent?.as(type) - } - - func getEnclosingClosureNode() -> ClosureExprSyntax? { - return getEnclosingNode(ClosureExprSyntax.self) - } -} - -extension Syntax { - func isDescendent(of node: Syntax) -> Bool { - return hasAncestor { $0 == node } - } - - // TODO (Le): should we consider self as ancestor of self like this ? - func hasAncestor(_ predicate: (Syntax) -> Bool) -> Bool { - if predicate(self) { return true } - var parent = self.parent - while parent != nil { - if predicate(parent!) { - return true - } - parent = parent?.parent - } - return false - } -} - -public extension ExprSyntax { - /// Returns the enclosing function call to which the current expr is passed as argument. We also return the corresponding - /// argument of the current expr, or nil if current expr is trailing closure - func getEnclosingFunctionCallExpression() -> (function: FunctionCallExprSyntax, argument: FunctionCallArgumentSyntax?)? { - var function: FunctionCallExprSyntax? - var argument: FunctionCallArgumentSyntax? - - if let parent = parent?.as(FunctionCallArgumentSyntax.self) { // Normal function argument - assert(parent.parent?.is(FunctionCallArgumentListSyntax.self) == true) - function = parent.parent?.parent?.as(FunctionCallExprSyntax.self) - argument = parent - } else if let parent = parent?.as(FunctionCallExprSyntax.self), - self.is(ClosureExprSyntax.self), - parent.trailingClosure == self.as(ClosureExprSyntax.self) - { // Trailing closure - function = parent - } - - guard function != nil else { - // Not function call - return nil - } - - return (function: function!, argument: argument) - } - - func isCalledExpr() -> Bool { - if let parentNode = parent?.as(FunctionCallExprSyntax.self) { - if parentNode.calledExpression == self { - return true - } - } - - return false - } - - var rangeInfo: (left: ExprSyntax?, op: TokenSyntax, right: ExprSyntax?)? { - if let expr = self.as(SequenceExprSyntax.self) { - let elements = expr.elements - guard elements.count == 3, let op = elements[1].rangeOperator else { - return nil - } - return (left: elements[elements.startIndex], op: op, right: elements[elements.index(before: elements.endIndex)]) - } - - if let expr = self.as(PostfixUnaryExprSyntax.self) { - if expr.operatorToken.isRangeOperator { - return (left: nil, op: expr.operatorToken, right: expr.expression) - } else { - return nil - } - } - - if let expr = self.as(PrefixOperatorExprSyntax.self) { - assert(expr.operatorToken != nil) - if expr.operatorToken!.isRangeOperator { - return (left: expr.postfixExpression, op: expr.operatorToken!, right: nil) - } else { - return nil - } - } - - return nil - } - - private var rangeOperator: TokenSyntax? { - guard let op = self.as(BinaryOperatorExprSyntax.self) else { - return nil - } - return op.operatorToken.isRangeOperator ? op.operatorToken : nil - } -} - -public extension TokenSyntax { - var isRangeOperator: Bool { - return text == "..." || text == "..<" - } -} - -public extension TypeSyntax { - var isOptional: Bool { - return self.is(OptionalTypeSyntax.self) || self.is(ImplicitlyUnwrappedOptionalTypeSyntax.self) - } - - var wrappedType: TypeSyntax { - if let optionalType = self.as(OptionalTypeSyntax.self) { - return optionalType.wrappedType - } - if let implicitOptionalType = self.as(ImplicitlyUnwrappedOptionalTypeSyntax.self) { - return implicitOptionalType.wrappedType - } - return self - } - - var tokens: [TokenSyntax]? { - if self == wrappedType { - if let type = self.as(MemberTypeIdentifierSyntax.self) { - if let base = type.baseType.tokens { - return base + [type.name] - } - return nil - } - if let type = self.as(SimpleTypeIdentifierSyntax.self) { - return [type.name] - } - return nil - } - return wrappedType.tokens - } - - var name: [String]? { - return tokens?.map { $0.text } - } - - var isClosure: Bool { - return wrappedType.is(FunctionTypeSyntax.self) - || (wrappedType.as(AttributedTypeSyntax.self))?.baseType.isClosure == true - || (wrappedType.as(TupleTypeSyntax.self)).flatMap { $0.elements.count == 1 && $0.elements[$0.elements.startIndex].type.isClosure } == true - } -} - -/// `gurad let a = b, ... `: `let a = b` is a OptionalBindingConditionSyntax -public extension OptionalBindingConditionSyntax { - func isGuardCondition() -> Bool { - return parent?.is(ConditionElementSyntax.self) == true - && parent?.parent?.is(ConditionElementListSyntax.self) == true - && parent?.parent?.parent?.is(GuardStmtSyntax.self) == true - } -} - -public extension FunctionCallExprSyntax { - var base: ExprSyntax? { - return calledExpression.baseAndSymbol?.base - } - - var symbol: TokenSyntax? { - return calledExpression.baseAndSymbol?.symbol - } -} - -// Only used for the FunctionCallExprSyntax extension above -private extension ExprSyntax { - var baseAndSymbol: (base: ExprSyntax?, symbol: TokenSyntax)? { - // base.symbol() - if let memberAccessExpr = self.as(MemberAccessExprSyntax.self) { - return (base: memberAccessExpr.base, symbol: memberAccessExpr.name) - } - - // symbol() - if let identifier = self.as(IdentifierExprSyntax.self) { - return (base: nil, symbol: identifier.identifier) - } - - // expr?.() - if let optionalChainingExpr = self.as(OptionalChainingExprSyntax.self) { - return optionalChainingExpr.expression.baseAndSymbol - } - - // expr() - if let specializeExpr = self.as(SpecializeExprSyntax.self) { - return specializeExpr.expression.baseAndSymbol - } - - assert(false, "Unhandled case") - return nil - } -} - -public extension FunctionParameterSyntax { - var isEscaping: Bool { - guard let attributedType = type?.as(AttributedTypeSyntax.self) else { - return false - } - - return attributedType.attributes?.contains(where: { $0.as(AttributeSyntax.self)?.attributeName.text == "escaping" }) == true - } -} - -/// Convenient -extension ArrayElementListSyntax { - subscript(_ i: Int) -> ArrayElementSyntax { - let index = self.index(startIndex, offsetBy: i) - return self[index] - } -} - -extension FunctionCallArgumentListSyntax { - subscript(_ i: Int) -> FunctionCallArgumentSyntax { - let index = self.index(startIndex, offsetBy: i) - return self[index] - } -} - -extension ExprListSyntax { - subscript(_ i: Int) -> ExprSyntax { - let index = self.index(startIndex, offsetBy: i) - return self[index] - } -} - -extension PatternBindingListSyntax { - subscript(_ i: Int) -> PatternBindingSyntax { - let index = self.index(startIndex, offsetBy: i) - return self[index] - } -} - -extension TupleTypeElementListSyntax { - subscript(_ i: Int) -> TupleTypeElementSyntax { - let index = self.index(startIndex, offsetBy: i) - return self[index] - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/Symbol.swift b/MemoryLeak/Sources/SwiftLeakCheck/Symbol.swift deleted file mode 100644 index 80b6327..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/Symbol.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// Symbol.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 03/01/2020. -// - -import SwiftSyntax - -public enum Symbol: Hashable { - case token(TokenSyntax) - case identifier(IdentifierExprSyntax) - - var node: Syntax { - switch self { - case .token(let node): return node._syntaxNode - case .identifier(let node): return node._syntaxNode - } - } - - var name: String { - switch self { - case .token(let node): return node.text - case .identifier(let node): return node.identifier.text - } - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/SyntaxRetrieval.swift b/MemoryLeak/Sources/SwiftLeakCheck/SyntaxRetrieval.swift deleted file mode 100644 index f1f95fc..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/SyntaxRetrieval.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// SyntaxRetrieval.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 09/12/2019. -// - -import SwiftSyntax -import SwiftSyntaxParser - -public enum SyntaxRetrieval { - public static func request(content: String) throws -> SourceFileSyntax { - return try SyntaxParser.parse( - source: content - ) - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/TypeDecl.swift b/MemoryLeak/Sources/SwiftLeakCheck/TypeDecl.swift deleted file mode 100644 index 5197f8c..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/TypeDecl.swift +++ /dev/null @@ -1,32 +0,0 @@ -// -// TypeDecl.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 04/01/2020. -// - -import SwiftSyntax - -// Class, struct, enum or extension -public struct TypeDecl: Equatable { - /// The name of the class/struct/enum/extension. - /// For class/struct/enum, it's 1 element - /// For extension, it could be multiple. Eg, extension X.Y.Z {...} - public let tokens: [TokenSyntax] - - public let inheritanceTypes: [InheritedTypeSyntax]? - - // Must be class/struct/enum/extension - public let scope: Scope - - public var name: [String] { - return tokens.map { $0.text } - } - - public var isExtension: Bool { - return scope.type == .extensionNode - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/TypeResolve.swift b/MemoryLeak/Sources/SwiftLeakCheck/TypeResolve.swift deleted file mode 100644 index 32e60c3..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/TypeResolve.swift +++ /dev/null @@ -1,76 +0,0 @@ -// -// TypeResolve.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 03/01/2020. -// - -import SwiftSyntax - -public indirect enum TypeResolve: Equatable { - case optional(base: TypeResolve) - case sequence(elementType: TypeResolve) - case dict - case tuple([TypeResolve]) - case name([String]) - case type(TypeDecl) - case unknown - - public var isOptional: Bool { - return self != self.wrappedType - } - - public var wrappedType: TypeResolve { - switch self { - case .optional(let base): - return base.wrappedType - case .sequence, - .dict, - .tuple, - .name, - .type, - .unknown: - return self - } - } - - public var name: [String]? { - switch self { - case .optional(let base): - return base.name - case .name(let tokens): - return tokens - case .type(let typeDecl): - return typeDecl.name - case .sequence, - .dict, - .tuple, - .unknown: - return nil - } - } - - public var sequenceElementType: TypeResolve { - switch self { - case .optional(let base): - return base.sequenceElementType - case .sequence(let elementType): - return elementType - case .dict, - .tuple, - .name, - .type, - .unknown: - return .unknown - } - } -} - -internal extension TypeResolve { - var isInt: Bool { - return name == ["Int"] - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/Utility.swift b/MemoryLeak/Sources/SwiftLeakCheck/Utility.swift deleted file mode 100644 index 8dc7e23..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/Utility.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// Utility.swift -// SwiftLeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 05/12/2019. -// - -import Foundation - -extension Collection where Index == Int { - subscript (safe index: Int) -> Element? { - if index < 0 || index >= count { - return nil - } - return self[index] - } -} diff --git a/MemoryLeak/Sources/SwiftLeakCheck/Variable.swift b/MemoryLeak/Sources/SwiftLeakCheck/Variable.swift deleted file mode 100644 index c6a0c5f..0000000 --- a/MemoryLeak/Sources/SwiftLeakCheck/Variable.swift +++ /dev/null @@ -1,329 +0,0 @@ -// -// Variable.swift -// LeakCheck -// -// Copyright 2020 Grabtaxi Holdings PTE LTE (GRAB), All rights reserved. -// Use of this source code is governed by an MIT-style license that can be found in the LICENSE file -// -// Created by Hoang Le Pham on 27/10/2019. -// - -import SwiftSyntax - -public enum RawVariable { - case capture(capturedNode: IdentifierExprSyntax) - case param(token: TokenSyntax) - case binding(token: TokenSyntax, valueNode: ExprSyntax?) - - var token: TokenSyntax { - switch self { - case .capture(let capturedNode): return capturedNode.identifier - case .binding(let token, _): return token - case .param(let token): return token - } - } -} - -indirect enum TypeInfo { - case exact(TypeSyntax) - case inferedFromExpr(ExprSyntax) - case inferedFromSequence(ExprSyntax) - case inferedFromTuple(tupleType: TypeInfo, index: Int) - case inferedFromClosure(ClosureExprSyntax, paramIndex: Int, paramCount: Int) -} - -// Represent a variable declaration. Eg -// var a = 1 -// let b = c // b is the Variable, c is not (c is a reference) -// block { [unowned x] in // x is a Variable -// func doSmth(a: Int, b: String) // a, b are Variables -public class Variable: Hashable, CustomStringConvertible { - public let raw: RawVariable - public var name: String { return raw.token.text } - let typeInfo: TypeInfo - public let memoryAttribute: MemoryAttribute? - public let scope: Scope - - var valueNode: ExprSyntax? { - switch raw { - case .binding(_, let valueNode): return valueNode - case .param, .capture: return nil - } - } - - var capturedNode: IdentifierExprSyntax? { - switch raw { - case .capture(let capturedNode): return capturedNode - case .binding, .param: return nil - } - } - - public var isStrong: Bool { - return memoryAttribute?.isStrong ?? true - } - - public var description: String { - return "\(raw)" - } - - private init(raw: RawVariable, - typeInfo: TypeInfo, - scope: Scope, - memoryAttribute: MemoryAttribute? = nil) { - self.raw = raw - self.typeInfo = typeInfo - self.scope = scope - self.memoryAttribute = memoryAttribute - } - - public static func from(_ node: ClosureCaptureItemSyntax, scope: Scope) -> Variable? { - assert(scope.scopeNode == node.enclosingScopeNode) - - guard let identifierExpr = node.expression.as(IdentifierExprSyntax.self) else { - // There're cases such as { [loggedInState.services] in ... }, which probably we don't need to care about - return nil - } - - let memoryAttribute: MemoryAttribute? = { - guard let specifier = node.specifier?.first else { - return nil - } - - assert(node.specifier!.count <= 1, "Unhandled case") - - guard let memoryAttribute = MemoryAttribute.from(specifier.text) else { - fatalError("Unhandled specifier \(specifier.text)") - } - return memoryAttribute - }() - - return Variable( - raw: .capture(capturedNode: identifierExpr), - typeInfo: .inferedFromExpr(ExprSyntax(identifierExpr)), - scope: scope, - memoryAttribute: memoryAttribute - ) - } - - public static func from(_ node: ClosureParamSyntax, scope: Scope) -> Variable { - guard let closure = node.getEnclosingClosureNode() else { - fatalError() - } - assert(scope.scopeNode == .closureNode(closure)) - - return Variable( - raw: .param(token: node.name), - typeInfo: .inferedFromClosure(closure, paramIndex: node.indexInParent, paramCount: node.parent!.children.count), - scope: scope - ) - } - - public static func from(_ node: FunctionParameterSyntax, scope: Scope) -> Variable { - assert(node.enclosingScopeNode == scope.scopeNode) - - guard let token = node.secondName ?? node.firstName else { - fatalError() - } - - assert(token.tokenKind != .wildcardKeyword, "Unhandled case") - assert(node.attributes == nil, "Unhandled case") - - guard let type = node.type else { - // Type is omited, must be used in closure signature - guard case let .closureNode(closureNode) = scope.scopeNode else { - fatalError("Only closure can omit the param type") - } - return Variable( - raw: .param(token: token), - typeInfo: .inferedFromClosure(closureNode, paramIndex: node.indexInParent, paramCount: node.parent!.children.count), - scope: scope - ) - } - - return Variable(raw: .param(token: token), typeInfo: .exact(type), scope: scope) - } - - public static func from(_ node: PatternBindingSyntax, scope: Scope) -> [Variable] { - guard let parent = node.parent?.as(PatternBindingListSyntax.self) else { - fatalError() - } - - assert(parent.parent?.is(VariableDeclSyntax.self) == true, "Unhandled case") - - func _typeFromNode(_ node: PatternBindingSyntax) -> TypeInfo { - // var a: Int - if let typeAnnotation = node.typeAnnotation { - return .exact(typeAnnotation.type) - } - // var a = value - if let value = node.initializer?.value { - return .inferedFromExpr(value) - } - // var a, b, .... = value - let indexOfNextNode = node.indexInParent + 1 - return _typeFromNode(parent[indexOfNextNode]) - } - - let type = _typeFromNode(node) - - if let identifier = node.pattern.as(IdentifierPatternSyntax.self) { - let memoryAttribute: MemoryAttribute? = { - if let modifier = node.parent?.parent?.as(VariableDeclSyntax.self)!.modifiers?.first { - return MemoryAttribute.from(modifier.name.text) - } - return nil - }() - - return [ - Variable( - raw: .binding(token: identifier.identifier, valueNode: node.initializer?.value), - typeInfo: type, - scope: scope, - memoryAttribute: memoryAttribute - ) - ] - } - - if let tuple = node.pattern.as(TuplePatternSyntax.self) { - return extractVariablesFromTuple(tuple, tupleType: type, tupleValue: node.initializer?.value, scope: scope) - } - - return [] - } - - public static func from(_ node: OptionalBindingConditionSyntax, scope: Scope) -> Variable? { - if let left = node.pattern.as(IdentifierPatternSyntax.self), let right = node.initializer?.value { - let type: TypeInfo - if let typeAnnotation = node.typeAnnotation { - type = .exact(typeAnnotation.type) - } else { - type = .inferedFromExpr(right) - } - - return Variable( - raw: .binding(token: left.identifier, valueNode: right), - typeInfo: type, - scope: scope, - memoryAttribute: .strong - ) - } - - return nil - } - - public static func from(_ node: ForInStmtSyntax, scope: Scope) -> [Variable] { - func _variablesFromPattern(_ pattern: PatternSyntax) -> [Variable] { - if let identifierPattern = pattern.as(IdentifierPatternSyntax.self) { - return [ - Variable( - raw: .binding(token: identifierPattern.identifier, valueNode: nil), - typeInfo: .inferedFromSequence(node.sequenceExpr), - scope: scope - ) - ] - } - - if let tuplePattern = pattern.as(TuplePatternSyntax.self) { - return extractVariablesFromTuple( - tuplePattern, - tupleType: .inferedFromSequence(node.sequenceExpr), - tupleValue: nil, - scope: scope - ) - } - - if pattern.is(WildcardPatternSyntax.self) { - return [] - } - - if let valueBindingPattern = pattern.as(ValueBindingPatternSyntax.self) { - return _variablesFromPattern(valueBindingPattern.valuePattern) - } - - assert(false, "Unhandled pattern in for statement: \(pattern)") - return [] - } - - return _variablesFromPattern(node.pattern) - } - - private static func extractVariablesFromTuple(_ tuplePattern: TuplePatternSyntax, - tupleType: TypeInfo, - tupleValue: ExprSyntax?, - scope: Scope) -> [Variable] { - return tuplePattern.elements.enumerated().flatMap { (index, element) -> [Variable] in - - let elementType: TypeInfo = .inferedFromTuple(tupleType: tupleType, index: index) - let elementValue: ExprSyntax? = { - if let tupleValue = tupleValue?.as(TupleExprSyntax.self) { - return tupleValue.elementList[index].expression - } - return nil - }() - - if let identifierPattern = element.pattern.as(IdentifierPatternSyntax.self) { - return [ - Variable( - raw: .binding(token: identifierPattern.identifier, valueNode: elementValue), - typeInfo: elementType, - scope: scope - ) - ] - } - - if let childTuplePattern = element.pattern.as(TuplePatternSyntax.self) { - return extractVariablesFromTuple( - childTuplePattern, - tupleType: elementType, - tupleValue: elementValue, - scope: scope - ) - } - - if element.pattern.is(WildcardPatternSyntax.self) { - return [] - } - - assertionFailure("I don't think there's any other kind") - return [] - } - } -} - -// MARK: - Hashable -public extension Variable { - static func == (_ lhs: Variable, _ rhs: Variable) -> Bool { - return lhs.raw.token == rhs.raw.token - } - - func hash(into hasher: inout Hasher) { - hasher.combine(raw.token) - } -} - -public enum MemoryAttribute: Hashable { - case weak - case unowned - case strong - - public var isStrong: Bool { - switch self { - case .weak, - .unowned: - return false - case .strong: - return true - } - } - - public static func from(_ text: String) -> MemoryAttribute? { - switch text { - case "weak": - return .weak - case "unowned": - return .unowned - default: - return nil - } - } -} diff --git a/MemoryLeak/Sources/SwiftLeakChecker/main.swift b/MemoryLeak/main.swift similarity index 83% rename from MemoryLeak/Sources/SwiftLeakChecker/main.swift rename to MemoryLeak/main.swift index 65cd247..5169b30 100644 --- a/MemoryLeak/Sources/SwiftLeakChecker/main.swift +++ b/MemoryLeak/main.swift @@ -4,6 +4,11 @@ // import Foundation +import SwiftLeakCheck + +func xcodeWarning(path: String, leak: Leak) -> String { + return "\(path):\(leak.line):\(leak.column): warning: `self` is strongly captured" +} enum CommandLineError: Error, LocalizedError { case missingFileName @@ -37,12 +42,10 @@ do { DispatchQueueRule() ] + CollectionRules.rules - let startDate = Date() let leaks = try leakDetector.detect(fileUrl) - let endDate = Date() - + leaks.forEach { leak in - print(leak.xcodeWarning(path: fileUrl.path)) + print(xcodeWarning(path: fileUrl.path, leak: leak)) } } catch {} }) @@ -53,3 +56,4 @@ do { print("\(error.localizedDescription)") } +