This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch GROOVY-11775
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 961370ce436194cd97053715ef1dfed3eda9fb4d
Author: Eric Milles <[email protected]>
AuthorDate: Fri Nov 14 14:09:39 2025 -0600

    GROOVY-11775: `ExpandoMetaClass`: prevent mixin metaclass duplication
---
 build.gradle                                       |  1 +
 src/main/java/groovy/lang/ExpandoMetaClass.java    |  9 ++++++--
 .../groovy/reflection/MixinInMetaClass.java        | 25 ++++++++++------------
 3 files changed, 19 insertions(+), 16 deletions(-)

diff --git a/build.gradle b/build.gradle
index 8b0ba28ec5..65abf18929 100644
--- a/build.gradle
+++ b/build.gradle
@@ -65,6 +65,7 @@ groovyCore {
     }
 
     bridgedClasses *supportClasses,
+            'groovy.lang.ExpandoMetaClass',
             'org.codehaus.groovy.classgen.Verifier',
             'org.codehaus.groovy.ast.tools.GeneralUtils',
             'org.codehaus.groovy.ast.stmt.TryCatchStatement',
diff --git a/src/main/java/groovy/lang/ExpandoMetaClass.java 
b/src/main/java/groovy/lang/ExpandoMetaClass.java
index d6e8d9ed0f..fa3c51d51f 100644
--- a/src/main/java/groovy/lang/ExpandoMetaClass.java
+++ b/src/main/java/groovy/lang/ExpandoMetaClass.java
@@ -448,8 +448,13 @@ public class ExpandoMetaClass extends MetaClassImpl 
implements GroovyObject {
         }
     }
 
-    public void addMixinClass(MixinInMetaClass mixin) {
-        mixinClasses.add(mixin);
+    @Deprecated
+    public void addMixinClass$$bridge(MixinInMetaClass mixin) {
+        addMixinClass(mixin);
+    }
+
+    public boolean addMixinClass(MixinInMetaClass mixin) {
+        return mixinClasses.add(mixin);
     }
 
     public Object castToMixedType(Object obj, Class type) {
diff --git a/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java 
b/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java
index 649fdce11e..b74ea2d502 100644
--- a/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java
+++ b/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java
@@ -38,9 +38,9 @@ import java.lang.annotation.Annotation;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
-import java.util.Objects;
 
 import static java.util.Arrays.stream;
+import static java.util.Objects.requireNonNull;
 
 public class MixinInMetaClass {
 
@@ -50,13 +50,11 @@ public class MixinInMetaClass {
     private final Map<Object, Object> mixinAssociations =
         new 
ManagedIdentityConcurrentMap<>(ManagedIdentityConcurrentMap.ReferenceType.SOFT);
 
-    public MixinInMetaClass(final ExpandoMetaClass emc, final CachedClass 
mixinClass) {
-        this.emc = emc;
-        this.mixinClass = mixinClass;
+    private MixinInMetaClass(final ExpandoMetaClass emc, final CachedClass 
mixinClass) {
+        this.emc = requireNonNull(emc);
+        this.mixinClass = requireNonNull(mixinClass);
         this.mixinConstructor = stream(mixinClass.getConstructors()).filter(it 
-> it.isPublic() && it.getParameterTypes().length == 0).findFirst()
                 .orElseThrow(() -> new GroovyRuntimeException("No default 
constructor for class " + mixinClass.getName() + "! Can't be mixed in."));
-
-        emc.addMixinClass(this);
     }
 
     public synchronized Object getMixinInstance(final Object object) {
@@ -103,6 +101,9 @@ public class MixinInMetaClass {
         for (Class<?> categoryClass : categoryClasses) {
             final CachedClass cachedCategoryClass = 
ReflectionCache.getCachedClass(categoryClass);
             final MixinInMetaClass mixin = new MixinInMetaClass(emc, 
cachedCategoryClass);
+            if (!emc.addMixinClass(mixin)) {
+                continue; // GROOVY-11775
+            }
 
             final MetaClass metaClass = 
GroovySystem.getMetaClassRegistry().getMetaClass(categoryClass);
             for (MetaProperty mp : metaClass.getProperties()) {
@@ -172,17 +173,13 @@ public class MixinInMetaClass {
 
     @Override
     public boolean equals(final Object that) {
-        return that == this
-            || (that instanceof MixinInMetaClass mc
-                && Objects.equals(mixinClass, mc.mixinClass));
+        return (that == this)
+            || (that instanceof MixinInMetaClass mmc
+                && emc.equals(mmc.emc) && mixinClass.equals(mmc.mixinClass));
     }
 
     @Override
     public int hashCode() {
-        int result = super.hashCode();
-        result = 31 * result + (emc != null ? emc.hashCode() : 0);
-        result = 31 * result + (mixinClass != null ? mixinClass.hashCode() : 
0);
-        result = 31 * result + (mixinConstructor != null ? 
mixinConstructor.hashCode() : 0);
-        return result;
+        return mixinClass.hashCode(); // GROOVY-11775
     }
 }

Reply via email to