Repository: aws-cloudformation/cfn-lint

Issue:
S3 slow cfn-lint (>30 minutes) after updating to 0.21.3
*cfn-lint version: 0.21.3

*Description of issue: We recently updated from 0.16.1 to 0.21.3 and something that used to take a few seconds now completes in over 30 minutes. I'm still trying to narrow down the issue, but it seems related to this CloudFormation resource:

```
  Bucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !If
      - hasBucketNameOverride
      - !Ref BucketNameOverride
      - !Sub
        - "${Account}-${BucketName}-${AWS::Region}"
        - Account: !FindInMap [ AccountMap, !Ref "AWS::AccountId", AccountName ]
      AccessControl: !If [ LogBucket, LogDeliveryWrite, Private ]
      BucketEncryption:
        ServerSideEncryptionConfiguration:
        - ServerSideEncryptionByDefault: !If
          - EncryptedWithKms
          - SSEAlgorithm: aws:kms
            KMSMasterKeyID: !If
            - PrimaryKMSKey
            - !Select [ 0, !Ref KMSKeyArns ]
            - !Select [ 1, !Ref KMSKeyArns ]
          - SSEAlgorithm: AES256
      LifecycleConfiguration:
        Rules:
        # Lifecycle policy for expiration of current versions of objects
        - Id: lifecycle-policy-current
          Status: !If [ Retention, Enabled, Disabled ]
          ExpirationInDays: !Ref 'CurrentVersionRetentionDays'
          AbortIncompleteMultipartUpload:
            DaysAfterInitiation: !Ref 'RetentionAbortIncomplete'
          Transitions:
          - !If
            - TransitionToIA
            - StorageClass: STANDARD_IA
              TransitionInDays: !Ref 'RetentionTransitionToIA'
            - !Ref 'AWS::NoValue'
          - !If
            - TransitionToGlacier
            - StorageClass: GLACIER
              TransitionInDays: !Ref 'RetentionTransitionToGlacier'
            - !Ref 'AWS::NoValue'

        # Lifecycle policy for non-current versions of objects
        - Id: lifecycle-policy-noncurrent
          Status: !If [ KeepNoncurrentVersionsForever, Disabled, Enabled ]
          NoncurrentVersionExpirationInDays: !Ref NonCurrentVersionRetentionDays
          NoncurrentVersionTransitions:
          - !If
            - TransitionToIA
            - StorageClass: STANDARD_IA
              TransitionInDays: !Ref 'RetentionTransitionToIA'
            - !Ref 'AWS::NoValue'
          - !If
            - TransitionToGlacier
            - StorageClass: GLACIER
              TransitionInDays: !Ref 'RetentionTransitionToGlacier'
            - !Ref 'AWS::NoValue'

      LoggingConfiguration:
        DestinationBucketName: !Sub
        - "${Account}-s3-logs-${AWS::Region}"
        - Account: !FindInMap [ AccountMap, !Ref "AWS::AccountId", AccountName ]
        LogFilePrefix: !Sub
        - "${Account}-${BucketName}-${AWS::Region}/"
        - Account: !FindInMap [ AccountMap, !Ref "AWS::AccountId", AccountName ]
      VersioningConfiguration:
        Status: !If [ Versioned, Enabled, Suspended ]
      PublicAccessBlockConfiguration: !If
      - EnablePublicBlock
      - BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      - !Ref 'AWS::NoValue'
      ReplicationConfiguration: !If
      - ReplicatedPrimary
      - Role: !GetAtt 'ReplicationRole.Arn'
        Rules:
        - Destination:
            Bucket: !Sub
            - "arn:aws:s3:::${Account}-${BucketName}-${SecondaryRegion}"
            - Account: !FindInMap [ AccountMap, !Ref "AWS::AccountId", AccountName ]
              SecondaryRegion: !Select [ 1, !Ref Regions ]
            EncryptionConfiguration: !If
            - EncryptedWithKms
            - ReplicaKmsKeyID: !Select [ 1, !Ref KMSKeyArns ]
            - !Ref 'AWS::NoValue'
          Prefix: ''
          Status: Enabled
          SourceSelectionCriteria: !If
          - EncryptedWithKms
          - SseKmsEncryptedObjects:
              Status: 'Enabled'
          - !Ref 'AWS::NoValue'
      - !Ref 'AWS::NoValue'
      CorsConfiguration: !If
      - Cors
      - CorsRules:
        - Id: CorsRuleId
          AllowedHeaders: !If [ CorsAllowedHeaders, !Ref CorsAllowedHeaders, !Ref 'AWS::NoValue' ]
          AllowedMethods: !If [ CorsAllowedMethods, !Ref CorsAllowedMethods, !Ref 'AWS::NoValue' ]
          AllowedOrigins: !If [ CorsAllowedOrigins, !Ref CorsAllowedOrigins, !Ref 'AWS::NoValue' ]
          ExposedHeaders: !If [ CorsExposedHeaders, !Ref CorsExposedHeaders, !Ref 'AWS::NoValue' ]
          MaxAge: !Ref CorsMaxAge
      - !Ref 'AWS::NoValue'
      WebsiteConfiguration: !If
      - isWebsite
      - IndexDocument: !Ref WebsiteIndex
        ErrorDocument: !If
        - isWebsiteError
        - !Ref WebsiteError
        - !Ref 'AWS::NoValue'
      - !Ref 'AWS::NoValue'
```

I imagine there's something in cfn-lint that's causing it to calculate too many permutations of this resource which is causing the issue.  Please let me know if there's something useful I can provide to help troubleshoot further.

Here's a quick and dirty time/test from my mac:
```
time cfn-lint -t template.yaml 


real	70m18.284s
user	59m23.627s
sys	0m12.033s
```



Discussion:
I have an idea on what it probably is.  Let me dig into it and see if I can make it faster for you.
Any chance you can do a `cfn-lint --debug`.  We should be able to see the rule that is kicking off and going slow.  Quickly setting up the conditions hasn't resulted in me being able to replicate the issue.  To your point we have added some logic for conditions and trying to build out scenarios.  There is a good chance with the amount of conditions there we are building too many scenarios.  I want to figure out if its a bug or something we just can't cover because the amount of scenarios.
I think I got it now.  No worries there.

Resolving patch:
diff --git a/src/cfnlint/__init__.py b/src/cfnlint/__init__.py
--- a/src/cfnlint/__init__.py
+++ b/src/cfnlint/__init__.py
@@ -95,7 +95,7 @@ def wrapper(self, filename, cfn, *args, **kwargs):
                 if not getattr(self, match_type):
                     return []
 
-                if match_type == 'resource_property_types':
+                if match_type == 'match_resource_properties':
                     if args[1] not in self.resource_property_types:
                         return []
                 elif match_type == 'match_resource_sub_properties':
diff --git a/src/cfnlint/conditions.py b/src/cfnlint/conditions.py
--- a/src/cfnlint/conditions.py
+++ b/src/cfnlint/conditions.py
@@ -383,8 +383,20 @@ def multiply_equals(currents, s_hash, sets, parameter_values):
             return results
 
         if not matched_conditions:
-            for condition in conditions:
-                results = self.multiply_conditions(results, condition, [True, False])
+            # fail safe to not create a lot of unrelated scenarios. Just test if they are true/false
+            # At this point this value is completely arbitrary and not configurable
+            if len(conditions) > 4:
+                LOGGER.info('Found %s conditions.  Limiting results to protect against heavy cpu time', len(conditions))
+                true_results = []
+                false_results = []
+                for condition in conditions:
+                    true_results = self.multiply_conditions(true_results, condition, [True])
+                    false_results = self.multiply_conditions(false_results, condition, [False])
+                results.extend(true_results)
+                results.extend(false_results)
+            else:
+                for condition in conditions:
+                    results = self.multiply_conditions(results, condition, [True, False])
 
             return results
 


Test patch: