Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added hash_value_overflow rule #2496

Merged
merged 14 commits into from
Dec 5, 2018
Merged
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
for large files.
[Niil Öhlin](https://github.com/niilohlin)

* Add new opt-in rule `hash_function` to warn against empty wrong hashValue
kimdv marked this conversation as resolved.
Show resolved Hide resolved
implementation.
kimdv marked this conversation as resolved.
Show resolved Hide resolved
[Kim de Vos](https://github.com/kimdv)
[#2108](https://github.com/realm/SwiftLint/issues/2108)

#### Bug Fixes

* Fix false positives in `redundant_objc_attribute` for private declarations
Expand Down
89 changes: 89 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
* [Function Default Parameter at End](#function-default-parameter-at-end)
* [Function Parameter Count](#function-parameter-count)
* [Generic Type Name](#generic-type-name)
* [Hash Function](#hash-function)
* [Identical Operands](#identical-operands)
* [Identifier Name](#identifier-name)
* [Implicit Getter](#implicit-getter)
Expand Down Expand Up @@ -7980,6 +7981,94 @@ enum Foo<↓type> {}



## Hash Function

Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version
--- | --- | --- | --- | --- | ---
`hash_function` | Enabled | No | lint | No | 4.2.0

Prefer using implementation `hash(into:)` instead of `hashValue`

### Examples

<details>
<summary>Non Triggering Examples</summary>

```swift
struct Foo: Hashable {
let bar: Int = 10

func hash(into hasher: inout Hasher) {
hasher.combine(bar)
}
}
```

```swift
class Foo: Hashable {
let bar: Int = 10

func hash(into hasher: inout Hasher) {
hasher.combine(bar)
}
}
```

```swift
var hashValue: Int { return 1 }
class Foo: Hashable {
}
```

```swift
class Foo: Hashable {
let bar: String = "Foo"

public var hashValue: String {
return bar
}
}
```

```swift
class Foo: Hashable {
let bar: String = "Foo"

public var hashValue: String {
get { return bar }
set { bar = newValue }
}
}
```

</details>
<details>
<summary>Triggering Examples</summary>

```swift
struct Foo: Hashable {
let bar: Int = 10

public ↓var hashValue: Int {
return bar
}
}
```

```swift
class Foo: Hashable {
let bar: Int = 10

public ↓var hashValue: Int {
return bar
}
}
```

</details>



## Identical Operands

Identifier | Enabled by default | Supports autocorrection | Kind | Analyzer | Minimum Swift Compiler Version
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ extension Configuration {
return type(of: lhs.rule).description.identifier == type(of: rhs.rule).description.identifier
}

fileprivate var hashValue: Int {
return type(of: rule).description.identifier.hashValue
public func hash(into hasher: inout Hasher) {
kimdv marked this conversation as resolved.
Show resolved Hide resolved
hasher.combine(type(of: rule).description.identifier)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ private struct RegexCacheKey: Hashable {
}

extension NSRegularExpression.Options: Hashable {
public var hashValue: Int {
return rawValue.hashValue
public func hash(into hasher: inout Hasher) {
hasher.combine(rawValue)
}
}

Expand Down
13 changes: 8 additions & 5 deletions Source/SwiftLintFramework/Models/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,18 @@ public struct Configuration: Hashable {
public private(set) var configurationPath: String? // if successfully loaded from a path
public let cachePath: String?

public var hashValue: Int {
public func hash(into hasher: inout Hasher) {
if let configurationPath = configurationPath {
return configurationPath.hashValue
hasher.combine(configurationPath)
} else if let rootPath = rootPath {
return rootPath.hashValue
hasher.combine(rootPath)
} else if let cachePath = cachePath {
return cachePath.hashValue
hasher.combine(cachePath)
} else {
hasher.combine(included)
hasher.combine(excluded)
hasher.combine(reporter)
}
return (included + excluded + [reporter]).reduce(0, { $0 ^ $1.hashValue })
}

internal var computedCacheDescription: String?
Expand Down
1 change: 1 addition & 0 deletions Source/SwiftLintFramework/Models/MasterRuleList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public let masterRuleList = RuleList(rules: [
FunctionDefaultParameterAtEndRule.self,
FunctionParameterCountRule.self,
GenericTypeNameRule.self,
HashFunctionRule.self,
IdenticalOperandsRule.self,
IdentifierNameRule.self,
ImplicitGetterRule.self,
Expand Down
97 changes: 97 additions & 0 deletions Source/SwiftLintFramework/Rules/Lint/HashFunctionRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import Foundation
import SourceKittenFramework

public struct HashFunctionRule: ASTRule, ConfigurationProviderRule, AutomaticTestableRule {
public var configuration = SeverityConfiguration(.warning)

public init() {}

public static let description = RuleDescription(
identifier: "hash_function",
name: "Hash Function",
description: "Prefer using implementation `hash(into:)` instead of `hashValue`",
kimdv marked this conversation as resolved.
Show resolved Hide resolved
kind: .lint,
minSwiftVersion: .fourDotTwo,
nonTriggeringExamples: [
"""
struct Foo: Hashable {
kimdv marked this conversation as resolved.
Show resolved Hide resolved
let bar: Int = 10

func hash(into hasher: inout Hasher) {
hasher.combine(bar)
}
}
""",
"""
class Foo: Hashable {
let bar: Int = 10

func hash(into hasher: inout Hasher) {
hasher.combine(bar)
}
}
""",
"""
var hashValue: Int { return 1 }
class Foo: Hashable { \n }
""",
"""
class Foo: Hashable {
let bar: String = "Foo"

public var hashValue: String {
return bar
}
}
""",
"""
class Foo: Hashable {
let bar: String = "Foo"

public var hashValue: String {
get { return bar }
set { bar = newValue }
}
}
"""
],
triggeringExamples: [
"""
struct Foo: Hashable {
let bar: Int = 10

public ↓var hashValue: Int {
return bar
}
}
""",
"""
class Foo: Hashable {
let bar: Int = 10

public ↓var hashValue: Int {
return bar
}
}
"""
]
)

// MARK: - ASTRule

public func validate(file: File,
kind: SwiftDeclarationKind,
dictionary: [String: SourceKitRepresentable]) -> [StyleViolation] {
guard kind == .varInstance,
dictionary.setterAccessibility == nil,
dictionary.typeName == "Int",
dictionary.name == "hashValue",
let offset = dictionary.offset else {
kimdv marked this conversation as resolved.
Show resolved Hide resolved
return []
}

return [StyleViolation(ruleDescription: type(of: self).description,
severity: configuration.severity,
location: Location(file: file, byteOffset: offset))]
}
}
18 changes: 11 additions & 7 deletions SwiftLint.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,9 @@
78F032481D7D614300BE709A /* OverridenSuperCallConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78F032471D7D614300BE709A /* OverridenSuperCallConfiguration.swift */; };
7C0C2E7A1D2866CB0076435A /* ExplicitInitRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7C0C2E791D2866CB0076435A /* ExplicitInitRule.swift */; };
820F451E21073D7200AA056A /* ConditionalReturnsOnNewlineRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 820F451D21073D7200AA056A /* ConditionalReturnsOnNewlineRuleTests.swift */; };
824AB64D2105C39F004B5A8F /* ConditionalReturnsOnNewlineConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 824AB64C2105C39F004B5A8F /* ConditionalReturnsOnNewlineConfiguration.swift */; };
823EDC6221020D850070B7CD /* MultilineLiteralBracketsRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 823EDC6121020D850070B7CD /* MultilineLiteralBracketsRule.swift */; };
82144ACC20F640F200B06695 /* VerticalWhitespaceBetweenCasesRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82144ACB20F640F200B06695 /* VerticalWhitespaceBetweenCasesRule.swift */; };
823EDC6221020D850070B7CD /* MultilineLiteralBracketsRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 823EDC6121020D850070B7CD /* MultilineLiteralBracketsRule.swift */; };
824AB64D2105C39F004B5A8F /* ConditionalReturnsOnNewlineConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 824AB64C2105C39F004B5A8F /* ConditionalReturnsOnNewlineConfiguration.swift */; };
825F19D11EEFF19700969EF1 /* ObjectLiteralRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 825F19D01EEFF19700969EF1 /* ObjectLiteralRuleTests.swift */; };
827169B31F488181003FB9AF /* ExplicitEnumRawValueRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 827169B21F488181003FB9AF /* ExplicitEnumRawValueRule.swift */; };
827169B51F48D712003FB9AF /* NoGroupingExtensionRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 827169B41F48D712003FB9AF /* NoGroupingExtensionRule.swift */; };
Expand Down Expand Up @@ -200,6 +200,7 @@
C2B3C1612106F78C00088928 /* ConfigurationAliasesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2B3C15F2106F78100088928 /* ConfigurationAliasesTests.swift */; };
C328A2F71E6759AE00A9E4D7 /* ExplicitTypeInterfaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C328A2F51E67595500A9E4D7 /* ExplicitTypeInterfaceRule.swift */; };
C3DE5DAC1E7DF9CA00761483 /* FatalErrorMessageRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DE5DAA1E7DF99B00761483 /* FatalErrorMessageRule.swift */; };
C3EF547821B5A4000009262F /* HashFunctionRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3EF547521B5A2190009262F /* HashFunctionRule.swift */; };
C946FECB1EAE67EE007DD778 /* LetVarWhitespaceRule.swift in Sources */ = {isa = PBXBuildFile; fileRef = C946FEC91EAE5E20007DD778 /* LetVarWhitespaceRule.swift */; };
C9802F2F1E0C8AEE008AB27F /* TrailingCommaRuleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9802F2E1E0C8AEE008AB27F /* TrailingCommaRuleTests.swift */; };
CC26ED07204DEB510013BBBC /* RuleIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC26ED05204DE86E0013BBBC /* RuleIdentifier.swift */; };
Expand Down Expand Up @@ -566,9 +567,9 @@
78F032471D7D614300BE709A /* OverridenSuperCallConfiguration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OverridenSuperCallConfiguration.swift; sourceTree = "<group>"; };
7C0C2E791D2866CB0076435A /* ExplicitInitRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExplicitInitRule.swift; sourceTree = "<group>"; };
820F451D21073D7200AA056A /* ConditionalReturnsOnNewlineRuleTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalReturnsOnNewlineRuleTests.swift; sourceTree = "<group>"; };
824AB64C2105C39F004B5A8F /* ConditionalReturnsOnNewlineConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalReturnsOnNewlineConfiguration.swift; sourceTree = "<group>"; };
823EDC6121020D850070B7CD /* MultilineLiteralBracketsRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineLiteralBracketsRule.swift; sourceTree = "<group>"; };
82144ACB20F640F200B06695 /* VerticalWhitespaceBetweenCasesRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalWhitespaceBetweenCasesRule.swift; sourceTree = "<group>"; };
823EDC6121020D850070B7CD /* MultilineLiteralBracketsRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineLiteralBracketsRule.swift; sourceTree = "<group>"; };
824AB64C2105C39F004B5A8F /* ConditionalReturnsOnNewlineConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalReturnsOnNewlineConfiguration.swift; sourceTree = "<group>"; };
825F19D01EEFF19700969EF1 /* ObjectLiteralRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectLiteralRuleTests.swift; sourceTree = "<group>"; };
827169B21F488181003FB9AF /* ExplicitEnumRawValueRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExplicitEnumRawValueRule.swift; sourceTree = "<group>"; };
827169B41F48D712003FB9AF /* NoGroupingExtensionRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NoGroupingExtensionRule.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -629,6 +630,7 @@
C2B3C15F2106F78100088928 /* ConfigurationAliasesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigurationAliasesTests.swift; sourceTree = "<group>"; };
C328A2F51E67595500A9E4D7 /* ExplicitTypeInterfaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExplicitTypeInterfaceRule.swift; sourceTree = "<group>"; };
C3DE5DAA1E7DF99B00761483 /* FatalErrorMessageRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FatalErrorMessageRule.swift; sourceTree = "<group>"; };
C3EF547521B5A2190009262F /* HashFunctionRule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashFunctionRule.swift; sourceTree = "<group>"; };
C946FEC91EAE5E20007DD778 /* LetVarWhitespaceRule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LetVarWhitespaceRule.swift; sourceTree = "<group>"; };
C9802F2E1E0C8AEE008AB27F /* TrailingCommaRuleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TrailingCommaRuleTests.swift; sourceTree = "<group>"; };
CC26ED05204DE86E0013BBBC /* RuleIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleIdentifier.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -997,6 +999,7 @@
E315B83B1DFA4BC500621B44 /* DynamicInlineRule.swift */,
62A3E95B209E078000547A86 /* EmptyXCTestMethodRule.swift */,
626B01B420A1735900D2C42F /* EmptyXCTestMethodRuleExamples.swift */,
C3EF547521B5A2190009262F /* HashFunctionRule.swift */,
D4E92D1E2137B4C9002EDD48 /* IdenticalOperandsRule.swift */,
D4441A27213279950020896F /* InertDeferRule.swift */,
C26330352073DAA200D7B4FD /* LowerACLThanParentRule.swift */,
Expand Down Expand Up @@ -1063,12 +1066,12 @@
D4EA77C91F81FACC00C315FB /* LiteralExpressionEndIdentationRule.swift */,
188B3FF1207D61040073C2D6 /* ModifierOrderRule.swift */,
BC8757392195CDD500CA7A74 /* ModifierOrderRuleExamples.swift */,
82F614F32106015100D23904 /* MultilineArgumentsBracketsRule.swift */,
82F614F32106015100D23904 /* MultilineArgumentsBracketsRule.swift */,
B25DCD071F7E9B5F0028A199 /* MultilineArgumentsRule.swift */,
B25DCD091F7E9BB50028A199 /* MultilineArgumentsRuleExamples.swift */,
3ABE19CD20B7CDE0009C2EC2 /* MultilineFunctionChainsRule.swift */,
823EDC6121020D850070B7CD /* MultilineLiteralBracketsRule.swift */,
82F614F12106014500D23904 /* MultilineParametersBracketsRule.swift */,
823EDC6121020D850070B7CD /* MultilineLiteralBracketsRule.swift */,
82F614F12106014500D23904 /* MultilineParametersBracketsRule.swift */,
6238AE411ED4D734006C3601 /* MultilineParametersRule.swift */,
621061BE1ED57E640082D51E /* MultilineParametersRuleExamples.swift */,
BB00B4E71F5216070079869F /* MultipleClosuresWithTrailingClosureRule.swift */,
Expand Down Expand Up @@ -1895,6 +1898,7 @@
006ECFC41C44E99E00EF6364 /* LegacyConstantRule.swift in Sources */,
82FE254120F604CB00295958 /* VerticalWhitespaceClosingBracesRule.swift in Sources */,
429644B61FB0A9B400D75128 /* SortedFirstLastRule.swift in Sources */,
C3EF547821B5A4000009262F /* HashFunctionRule.swift in Sources */,
31F1B6CC1F60BF4500A57456 /* SwitchCaseAlignmentRule.swift in Sources */,
E88DEA731B0984C400A66CB0 /* String+SwiftLint.swift in Sources */,
E88198591BEA95F100333A11 /* LeadingWhitespaceRule.swift in Sources */,
Expand Down
7 changes: 7 additions & 0 deletions Tests/LinuxMain.swift
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,12 @@ extension GlobTests {
]
}

extension HashFunctionRuleTests {
static var allTests: [(String, (HashFunctionRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration)
]
}

extension IdenticalOperandsRuleTests {
static var allTests: [(String, (IdenticalOperandsRuleTests) -> () throws -> Void)] = [
("testWithDefaultConfiguration", testWithDefaultConfiguration)
Expand Down Expand Up @@ -1406,6 +1412,7 @@ XCTMain([
testCase(FunctionParameterCountRuleTests.allTests),
testCase(GenericTypeNameRuleTests.allTests),
testCase(GlobTests.allTests),
testCase(HashFunctionRuleTests.allTests),
testCase(IdenticalOperandsRuleTests.allTests),
testCase(IdentifierNameRuleTests.allTests),
testCase(ImplicitGetterRuleTests.allTests),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,12 @@ class FunctionDefaultParameterAtEndRuleTests: XCTestCase {
}
}

class HashFunctionRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(HashFunctionRule.description)
}
}

class IdenticalOperandsRuleTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule(IdenticalOperandsRule.description)
Expand Down