diff --git a/src/main/java/groovy/lang/MetaClassImpl.java b/src/main/java/groovy/lang/MetaClassImpl.java index 427d4b4e608..09c127fe9e4 100644 --- a/src/main/java/groovy/lang/MetaClassImpl.java +++ b/src/main/java/groovy/lang/MetaClassImpl.java @@ -1824,18 +1824,18 @@ public Object getProperty(final Class sender, final Object object, final String //---------------------------------------------------------------------- Tuple2 methodAndProperty = createMetaMethodAndMetaProperty(sender, name, useSuper, isStatic); MetaMethod method = methodAndProperty.getV1(); + MetaProperty prop = methodAndProperty.getV2(); - if (method == null || isSpecialProperty(name)) { + if (method == null || isSpecialProperty(name) || isVisibleProperty(prop, method, sender)) { //------------------------------------------------------------------ // public field //------------------------------------------------------------------ - MetaProperty mp = methodAndProperty.getV2(); - if (mp != null && mp.isPublic()) { + if (prop != null && prop.isPublic()) { try { - return mp.getProperty(object); + return prop.getProperty(object); } catch (GroovyRuntimeException e) { // can't access the field directly but there may be a getter - mp = null; + prop = null; } } @@ -1849,9 +1849,9 @@ public Object getProperty(final Class sender, final Object object, final String //------------------------------------------------------------------ // non-public field //------------------------------------------------------------------ - if (mp != null) { + if (prop != null) { try { - return mp.getProperty(object); + return prop.getProperty(object); } catch (GroovyRuntimeException e) { } } @@ -1946,14 +1946,14 @@ public void setProperty(Object object, Object newValue) { //---------------------------------------------------------------------- Tuple2 methodAndProperty = createMetaMethodAndMetaProperty(sender, name, useSuper, isStatic); MetaMethod method = methodAndProperty.getV1(); + MetaProperty prop = methodAndProperty.getV2(); - if (method == null || isSpecialProperty(name)) { + if (method == null || isSpecialProperty(name) || isVisibleProperty(prop, method, sender)) { //------------------------------------------------------------------ // public field //------------------------------------------------------------------ - MetaProperty mp = methodAndProperty.getV2(); - if (mp != null && mp.isPublic()) { - return mp; + if (prop != null && prop.isPublic()) { + return prop; } //------------------------------------------------------------------ @@ -1976,8 +1976,8 @@ public void setProperty(Object object, Object newValue) { //------------------------------------------------------------------ // non-public field //------------------------------------------------------------------ - if (mp != null) { - return mp; + if (prop != null) { + return prop; } } @@ -2112,6 +2112,21 @@ private boolean isSpecialProperty(final String name) { return "class".equals(name) || (isMap && ("empty".equals(name))); } + private boolean isVisibleProperty(final MetaProperty field, final MetaMethod method, final Class sender) { + if (!(field instanceof CachedField)) return false; + + if (field.isPrivate()) return false; + + Class owner = ((CachedField) field).getDeclaringClass(); + // ensure access originates within the type hierarchy of the field owner + if (owner.equals(sender) || !owner.isAssignableFrom(sender)) return false; + + if (!field.isPublic() && !field.isProtected() && !inSamePackage(owner, sender)) return false; + + // GROOVY-8283: non-private field that hides class access method + return !owner.isAssignableFrom(method.getDeclaringClass().getTheClass()) && !method.getDeclaringClass().isInterface(); + } + private Tuple2 createMetaMethodAndMetaProperty(final Class sender, final String name, final boolean useSuper, final boolean isStatic) { MetaMethod method = null; MetaProperty mp = getMetaProperty(sender, name, useSuper, isStatic); @@ -2772,7 +2787,7 @@ public void setProperty(final Class sender, final Object object, final String na //---------------------------------------------------------------------- // field //---------------------------------------------------------------------- - if (method == null && field != null + if (field != null && (method == null || isVisibleProperty(field, method, sender)) && (!isMap || isStatic // GROOVY-8065 || field.isPublic())) { // GROOVY-11367 if (!field.isFinal()) { diff --git a/src/test/groovy/bugs/Groovy8283.groovy b/src/test/groovy/bugs/Groovy8283.groovy new file mode 100644 index 00000000000..ec99829e2dd --- /dev/null +++ b/src/test/groovy/bugs/Groovy8283.groovy @@ -0,0 +1,132 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package groovy.bugs + +import org.junit.Test + +import static groovy.test.GroovyAssert.assertScript + +final class Groovy8283 { + + @Test + void testReadFieldPropertyShadowing() { + def shell = new GroovyShell() + shell.parse '''package p + class A {} + class B {} + class C { + protected A foo = new A() + A getFoo() { return foo } + } + class D extends C { + protected B foo = new B() // hides A#foo; should hide A#getFoo in subclasses + } + ''' + assertScript shell, '''import p.* + class E extends D { + void test() { + assert foo.class == B + assert this.foo.class == B + assert this.@foo.class == B + assert this.getFoo().getClass() == A + + def that = new E() + assert that.foo.class == B + assert that.@foo.class == B + assert that.getFoo().getClass() == A + } + } + + new E().test() + assert new E().foo.class == A // not the field from this perspective + ''' + } + + @Test + void testWriteFieldPropertyShadowing() { + def shell = new GroovyShell() + shell.parse '''package p + class A {} + class B {} + class C { + boolean setter + protected A foo = new A() + A getFooA() { return this.@foo } + void setFoo(A a) { setter = true; this.@foo = a } + } + class D extends C { + protected B foo = new B() // hides A#foo; should hide A#setFoo in subclasses + B getFooB() { return this.@foo } + } + ''' + assertScript shell, '''import p.* + class E extends D { + void test1() { + foo = null + assert !setter + assert fooA != null + assert fooB == null + } + void test2() { + this.foo = null + assert !setter + assert fooA != null + assert fooB == null + } + void test3() { + this.@foo = null + assert !setter + assert fooA != null + assert fooB == null + } + void test4() { + this.setFoo(null) + assert setter + assert fooA == null + assert fooB != null + } + void test5() { + def that = new E() + that.foo = null + assert !that.setter + assert that.fooA != null + assert that.fooB == null + + that = new E() + that.@foo = null + assert !that.setter + assert that.fooA != null + assert that.fooB == null + + that = new E() + that.setFoo(null) + assert that.setter + assert that.fooA == null + assert that.fooB != null + } + } + + new E().test1() + new E().test2() + new E().test3() + new E().test4() + new E().test5() + ''' + } +}