Paul King created GROOVY-7417: --------------------------------- Summary: @EqualsAndHashCode inconsistent when using boolean properties for classes with explicit getters Key: GROOVY-7417 URL: https://issues.apache.org/jira/browse/GROOVY-7417 Project: Groovy Issue Type: Bug Affects Versions: 2.4.3 Reporter: Christopher Smith Priority: Critical
I have the following class representing a location in Amazon S3. Depending on some inner business logic, I often need to split the object key into a prefix and a file name, so I access them all through the {{getKey()}} method; with S3, the only thing that matters is the final concatenated string. The generated {{equals}} method incorrectly returns true for any two objects with the same bucket, ignoring the key property. (I discovered this when I got some interesting results out of a JSR-330 cache.) I have marked this issue critical because it is likely to cause immediate security vulnerabilities and data loss when unequal objects are found equal. {code} @CompileStatic @EqualsAndHashCode(includes = ['bucket', 'key']) final class S3ImageLocation implements ImageLocation { @NotNull final String bucket final String prefix @NotNull final String subKey @PersistenceConstructor S3ImageLocation(String bucket, String prefix, String subKey) { this.bucket = bucket this.prefix = prefix this.subKey = subKey } S3ImageLocation(String bucket, String subKey) { this(bucket, null, subKey) } @JsonIgnore String getKey() { prefix ? "$prefix/$subKey" : subKey } @Override String toString() { "s3://$bucket/$key" } S3Location toBlitlineLocation() { new S3Location(bucket, key) } } {code} Both of the generated methods appear to be including {{bucket}} twice instead of including {{bucket}} and {{key}}. see :7 and :29 {code} public int hashCode(); Code: 0: invokestatic #93 // Method org/codehaus/groovy/util/HashCodeHelper.initHash:()I 3: istore_1 4: iload_1 5: pop 6: aload_0 7: ldc #94 // String bucket 9: invokestatic #100 // Method org/codehaus/groovy/runtime/InvokerHelper.getProperty:(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object; 12: aload_0 13: invokestatic #106 // Method org/codehaus/groovy/runtime/DefaultGroovyMethods.is:(Ljava/lang/Object;Ljava/lang/Object;)Z 16: ifne 23 19: iconst_1 20: goto 24 23: iconst_0 24: ifeq 42 27: iload_1 28: aload_0 29: ldc #94 // String bucket 31: invokestatic #100 // Method org/codehaus/groovy/runtime/InvokerHelper.getProperty:(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/Object; 34: invokestatic #110 // Method org/codehaus/groovy/util/HashCodeHelper.updateHash:(ILjava/lang/Object;)I 37: istore_2 38: iload_2 39: istore_1 40: iload_2 41: pop 42: iload_1 43: ireturn 44: ldc #113 // int 0 46: ireturn {code} see :152/:171 and :208/:219 {code} public boolean equals(java.lang.Object); Code: 0: aload_1 1: ifnonnull 8 4: iconst_1 5: goto 9 8: iconst_0 9: ifeq 19 12: getstatic #129 // Field java/lang/Boolean.FALSE:Ljava/lang/Boolean; 15: invokestatic #58 // Method org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox:(Ljava/lang/Object;)Z 18: ireturn 19: aload_0 20: aload_1 21: invokestatic #106 // Method org/codehaus/groovy/runtime/DefaultGroovyMethods.is:(Ljava/lang/Object;Ljava/lang/Object;)Z 24: ifeq 34 27: getstatic #132 // Field java/lang/Boolean.TRUE:Ljava/lang/Boolean; 30: invokestatic #58 // Method org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox:(Ljava/lang/Object;)Z 33: ireturn 34: aload_1 35: instanceof #2 // class com/artsquare/studio/img/s3/S3ImageLocation 38: ifne 45 41: iconst_1 42: goto 46 45: iconst_0 46: ifeq 56 49: getstatic #129 // Field java/lang/Boolean.FALSE:Ljava/lang/Boolean; 52: invokestatic #58 // Method org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox:(Ljava/lang/Object;)Z 55: ireturn 56: aload_1 57: ldc #2 // class com/artsquare/studio/img/s3/S3ImageLocation 59: invokestatic #138 // Method org/codehaus/groovy/runtime/ScriptBytecodeAdapter.castToType:(Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Object; 62: checkcast #2 // class com/artsquare/studio/img/s3/S3ImageLocation 65: astore_2 66: aload_2 67: pop 68: aload_2 69: aload_0 70: invokevirtual #140 // Method canEqual:(Ljava/lang/Object;)Z 73: ifne 80 76: iconst_1 77: goto 81 80: iconst_0 81: ifeq 91 84: getstatic #129 // Field java/lang/Boolean.FALSE:Ljava/lang/Boolean; 87: invokestatic #58 // Method org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox:(Ljava/lang/Object;)Z 90: ireturn 91: aload_0 92: invokevirtual #143 // Method getBucket:()Ljava/lang/String; 95: aload_2 96: invokevirtual #143 // Method getBucket:()Ljava/lang/String; 99: invokestatic #106 // Method org/codehaus/groovy/runtime/DefaultGroovyMethods.is:(Ljava/lang/Object;Ljava/lang/Object;)Z 102: ifne 109 105: iconst_1 106: goto 110 109: iconst_0 110: ifeq 274 113: aload_0 114: invokevirtual #143 // Method getBucket:()Ljava/lang/String; 117: aload_0 118: invokestatic #106 // Method org/codehaus/groovy/runtime/DefaultGroovyMethods.is:(Ljava/lang/Object;Ljava/lang/Object;)Z 121: ifeq 147 124: aload_2 125: invokevirtual #143 // Method getBucket:()Ljava/lang/String; 128: aload_2 129: invokestatic #106 // Method org/codehaus/groovy/runtime/DefaultGroovyMethods.is:(Ljava/lang/Object;Ljava/lang/Object;)Z 132: ifne 139 135: iconst_1 136: goto 140 139: iconst_0 140: ifeq 147 143: iconst_1 144: goto 148 147: iconst_0 148: ifne 189 151: aload_0 152: invokevirtual #143 // Method getBucket:()Ljava/lang/String; 155: aload_0 156: invokestatic #106 // Method org/codehaus/groovy/runtime/DefaultGroovyMethods.is:(Ljava/lang/Object;Ljava/lang/Object;)Z 159: ifne 166 162: iconst_1 163: goto 167 166: iconst_0 167: ifeq 185 170: aload_2 171: invokevirtual #143 // Method getBucket:()Ljava/lang/String; 174: aload_2 175: invokestatic #106 // Method org/codehaus/groovy/runtime/DefaultGroovyMethods.is:(Ljava/lang/Object;Ljava/lang/Object;)Z 178: ifeq 185 181: iconst_1 182: goto 186 185: iconst_0 186: ifeq 193 189: iconst_1 190: goto 194 193: iconst_0 194: ifeq 207 197: getstatic #129 // Field java/lang/Boolean.FALSE:Ljava/lang/Boolean; 200: invokestatic #58 // Method org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox:(Ljava/lang/Object;)Z 203: ireturn 204: goto 274 207: aload_0 208: invokevirtual #143 // Method getBucket:()Ljava/lang/String; 211: aload_0 212: invokestatic #106 // Method org/codehaus/groovy/runtime/DefaultGroovyMethods.is:(Ljava/lang/Object;Ljava/lang/Object;)Z 215: ifeq 233 218: aload_2 219: invokevirtual #143 // Method getBucket:()Ljava/lang/String; 222: aload_2 223: invokestatic #106 // Method org/codehaus/groovy/runtime/DefaultGroovyMethods.is:(Ljava/lang/Object;Ljava/lang/Object;)Z 226: ifeq 233 229: iconst_1 230: goto 234 233: iconst_0 234: ifne 241 237: iconst_1 238: goto 242 241: iconst_0 242: ifeq 274 245: aload_0 246: invokevirtual #143 // Method getBucket:()Ljava/lang/String; 249: aload_2 250: invokevirtual #143 // Method getBucket:()Ljava/lang/String; 253: invokestatic #146 // Method org/codehaus/groovy/runtime/ScriptBytecodeAdapter.compareEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z 256: ifne 263 259: iconst_1 260: goto 264 263: iconst_0 264: ifeq 274 267: getstatic #129 // Field java/lang/Boolean.FALSE:Ljava/lang/Boolean; 270: invokestatic #58 // Method org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox:(Ljava/lang/Object;)Z 273: ireturn 274: getstatic #132 // Field java/lang/Boolean.TRUE:Ljava/lang/Boolean; 277: invokestatic #58 // Method org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox:(Ljava/lang/Object;)Z 280: ireturn 281: ldc #113 // int 0 283: invokestatic #122 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 286: invokestatic #58 // Method org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.booleanUnbox:(Ljava/lang/Object;)Z 289: ireturn {code} -- This message was sent by Atlassian JIRA (v6.3.4#6332)