You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by sh...@apache.org on 2016/01/14 02:28:11 UTC

groovy git commit: GROOVY-7705: Add bridge methods for setting private fields of outer class from inner class or closure (closes #229)

Repository: groovy
Updated Branches:
  refs/heads/master 606891d85 -> e1e60f616


GROOVY-7705: Add bridge methods for setting private fields of outer class from inner class or closure (closes #229)


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/e1e60f61
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/e1e60f61
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/e1e60f61

Branch: refs/heads/master
Commit: e1e60f616b38d09e9a23a90f2656d607f75f0d51
Parents: 606891d
Author: Shil Sinha <sh...@gmail.com>
Authored: Wed Dec 9 19:23:24 2015 -0500
Committer: Shil Sinha <sh...@apache.org>
Committed: Wed Jan 13 20:27:37 2016 -0500

----------------------------------------------------------------------
 ...ypesBinaryExpressionMultiTypeDispatcher.java |  57 +++++++
 .../sc/StaticCompilationMetadataKeys.java       |   3 +-
 .../transform/sc/StaticCompilationVisitor.java  |  43 +++--
 .../VariableExpressionTransformer.java          |   3 +
 .../stc/StaticTypeCheckingVisitor.java          |  25 +--
 .../groovy/transform/stc/StaticTypesMarker.java |   1 +
 .../FieldsAndPropertiesStaticCompileTest.groovy | 163 +++++++++++++++++++
 7 files changed, 273 insertions(+), 22 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/e1e60f61/src/main/org/codehaus/groovy/classgen/asm/sc/StaticTypesBinaryExpressionMultiTypeDispatcher.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/classgen/asm/sc/StaticTypesBinaryExpressionMultiTypeDispatcher.java b/src/main/org/codehaus/groovy/classgen/asm/sc/StaticTypesBinaryExpressionMultiTypeDispatcher.java
index a1088c4..9025665 100644
--- a/src/main/org/codehaus/groovy/classgen/asm/sc/StaticTypesBinaryExpressionMultiTypeDispatcher.java
+++ b/src/main/org/codehaus/groovy/classgen/asm/sc/StaticTypesBinaryExpressionMultiTypeDispatcher.java
@@ -29,6 +29,7 @@ import org.codehaus.groovy.classgen.asm.*;
 import org.codehaus.groovy.runtime.MetaClassHelper;
 import org.codehaus.groovy.syntax.Token;
 import org.codehaus.groovy.syntax.Types;
+import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys;
 import org.codehaus.groovy.transform.sc.StaticCompilationVisitor;
 import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
 import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor;
@@ -38,6 +39,7 @@ import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 
 import java.lang.reflect.Modifier;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicInteger;
 
 import static org.codehaus.groovy.ast.ClassHelper.*;
@@ -53,6 +55,7 @@ import static org.codehaus.groovy.transform.sc.StaticCompilationVisitor.*;
 public class StaticTypesBinaryExpressionMultiTypeDispatcher extends BinaryExpressionMultiTypeDispatcher implements Opcodes {
 
     private final AtomicInteger labelCounter = new AtomicInteger();
+    private static final MethodNode CLOSURE_GETTHISOBJECT_METHOD = CLOSURE_TYPE.getMethod("getThisObject", new Parameter[0]);
 
 
     public StaticTypesBinaryExpressionMultiTypeDispatcher(WriterController wc) {
@@ -298,6 +301,60 @@ public class StaticTypesBinaryExpressionMultiTypeDispatcher extends BinaryExpres
                 call.visit(controller.getAcg());
                 return true;
             }
+            if (isThisExpression && !controller.isInClosure()) {
+                receiverType = controller.getClassNode();
+            }
+            if (makeSetPrivateFieldWithBridgeMethod(receiver, receiverType, property, arguments, safe, spreadSafe, implicitThis)) return true;
+        }
+        return false;
+    }
+
+    @SuppressWarnings("unchecked")
+    private boolean makeSetPrivateFieldWithBridgeMethod(final Expression receiver, final ClassNode receiverType, final String fieldName, final Expression arguments, final boolean safe, final boolean spreadSafe, final boolean implicitThis) {
+        WriterController controller = getController();
+        FieldNode field = receiverType.getField(fieldName);
+        ClassNode outerClass = receiverType.getOuterClass();
+        if (field == null && implicitThis && outerClass != null && !receiverType.isStaticClass()) {
+            Expression pexp;
+            if (controller.isInClosure()) {
+                MethodCallExpression mce = new MethodCallExpression(
+                    new VariableExpression("this"),
+                    "getThisObject",
+                    ArgumentListExpression.EMPTY_ARGUMENTS
+                );
+                mce.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, controller.getOutermostClass());
+                mce.setImplicitThis(true);
+                mce.setMethodTarget(CLOSURE_GETTHISOBJECT_METHOD);
+                pexp = new CastExpression(controller.getOutermostClass(),mce);
+            } else {
+                pexp = new PropertyExpression(
+                    new ClassExpression(outerClass),
+                    "this"
+                );
+                ((PropertyExpression)pexp).setImplicitThis(true);
+            }
+            pexp.putNodeMetaData(StaticTypesMarker.INFERRED_TYPE, outerClass);
+            pexp.setSourcePosition(receiver);
+            return makeSetPrivateFieldWithBridgeMethod(pexp, outerClass, fieldName, arguments, safe, spreadSafe, true);
+        }
+        ClassNode classNode = controller.getClassNode();
+        if (field != null && Modifier.isPrivate(field.getModifiers())
+            && (StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(receiverType, classNode) || StaticInvocationWriter.isPrivateBridgeMethodsCallAllowed(classNode,receiverType))
+            && !receiverType.equals(classNode)) {
+            Map<String, MethodNode> mutators = (Map<String, MethodNode>) receiverType.redirect().getNodeMetaData(StaticCompilationMetadataKeys.PRIVATE_FIELDS_MUTATORS);
+            if (mutators != null) {
+                MethodNode methodNode = mutators.get(fieldName);
+                if (methodNode != null) {
+                    MethodCallExpression mce = new MethodCallExpression(receiver, methodNode.getName(),
+                        new ArgumentListExpression(field.isStatic()?new ConstantExpression(null):receiver, arguments));
+                        mce.setMethodTarget(methodNode);
+                        mce.setSafe(safe);
+                        mce.setSpreadSafe(spreadSafe);
+                    mce.setImplicitThis(implicitThis);
+                    mce.visit(controller.getAcg());
+                    return true;
+                }
+            }
         }
         return false;
     }

http://git-wip-us.apache.org/repos/asf/groovy/blob/e1e60f61/src/main/org/codehaus/groovy/transform/sc/StaticCompilationMetadataKeys.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/transform/sc/StaticCompilationMetadataKeys.java b/src/main/org/codehaus/groovy/transform/sc/StaticCompilationMetadataKeys.java
index c09ee01..3409368 100644
--- a/src/main/org/codehaus/groovy/transform/sc/StaticCompilationMetadataKeys.java
+++ b/src/main/org/codehaus/groovy/transform/sc/StaticCompilationMetadataKeys.java
@@ -27,7 +27,8 @@ public enum StaticCompilationMetadataKeys {
     STATIC_COMPILE_NODE, // used to mark a section of code as to be statically compiled
     BINARY_EXP_TARGET, // use to tell which method should be used in a binary expression
     PRIVATE_BRIDGE_METHODS, // private bridge methods are methods used by an outer class to access an inner class method
-    PRIVATE_FIELDS_ACCESSORS, // private constants methods are methods used by an outer class to access an outer class constant
+    PRIVATE_FIELDS_ACCESSORS, // private fields accessors are methods used by an inner class to access an outer class field
+    PRIVATE_FIELDS_MUTATORS, // private fields mutators are methods used by an inner class to set an outer class field
     PROPERTY_OWNER, // the type of the class which owns the property
     COMPONENT_TYPE, // for list.property expressions, we need the inferred component type
     RECEIVER_OF_DYNAMIC_PROPERTY // if a receiver is the receiver of a dynamic property (for mixed mode compilation)

http://git-wip-us.apache.org/repos/asf/groovy/blob/e1e60f61/src/main/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java b/src/main/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
index cf62c0f..dcdbfda 100644
--- a/src/main/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
+++ b/src/main/org/codehaus/groovy/transform/sc/StaticCompilationVisitor.java
@@ -27,10 +27,12 @@ import org.codehaus.groovy.ast.stmt.EmptyStatement;
 import org.codehaus.groovy.ast.stmt.ExpressionStatement;
 import org.codehaus.groovy.ast.stmt.ForStatement;
 import org.codehaus.groovy.ast.stmt.Statement;
+import org.codehaus.groovy.ast.tools.GeneralUtils;
 import org.codehaus.groovy.classgen.asm.*;
 import org.codehaus.groovy.classgen.asm.sc.StaticCompilationMopWriter;
 import org.codehaus.groovy.classgen.asm.sc.StaticTypesTypeChooser;
 import org.codehaus.groovy.control.SourceUnit;
+import org.codehaus.groovy.syntax.Token;
 import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
 import org.codehaus.groovy.transform.stc.StaticTypeCheckingVisitor;
 import org.codehaus.groovy.transform.stc.StaticTypesMarker;
@@ -165,35 +167,54 @@ public class StaticCompilationVisitor extends StaticTypeCheckingVisitor {
     }
 
     /**
-     * Adds special accessors for private constants so that inner classes can retrieve them.
+     * Adds special accessors and mutators for private fields so that inner classes can get/set them
      */
     @SuppressWarnings("unchecked")
     private void addPrivateFieldsAccessors(ClassNode node) {
         Set<ASTNode> accessedFields = (Set<ASTNode>) node.getNodeMetaData(StaticTypesMarker.PV_FIELDS_ACCESS);
-        if (accessedFields==null) return;
-        Map<String, MethodNode> privateConstantAccessors = (Map<String, MethodNode>) node.getNodeMetaData(PRIVATE_FIELDS_ACCESSORS);
-        if (privateConstantAccessors!=null) {
+        Set<ASTNode> mutatedFields = (Set<ASTNode>) node.getNodeMetaData(StaticTypesMarker.PV_FIELDS_MUTATION);
+        if (accessedFields == null && mutatedFields == null) return;
+        Map<String, MethodNode> privateFieldAccessors = (Map<String, MethodNode>) node.getNodeMetaData(PRIVATE_FIELDS_ACCESSORS);
+        Map<String, MethodNode> privateFieldMutators = (Map<String, MethodNode>) node.getNodeMetaData(PRIVATE_FIELDS_MUTATORS);
+        if (privateFieldAccessors != null || privateFieldMutators != null) {
             // already added
             return;
         }
         int acc = -1;
-        privateConstantAccessors = new HashMap<String, MethodNode>();
+        privateFieldAccessors = accessedFields != null ? new HashMap<String, MethodNode>() : null;
+        privateFieldMutators = mutatedFields != null ? new HashMap<String, MethodNode>() : null;
         final int access = Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC;
         for (FieldNode fieldNode : node.getFields()) {
-            if (accessedFields.contains(fieldNode)) {
-
+            boolean generateAccessor = accessedFields != null && accessedFields.contains(fieldNode);
+            boolean generateMutator = mutatedFields != null && mutatedFields.contains(fieldNode);
+            if (generateAccessor) {
                 acc++;
                 Parameter param = new Parameter(node.getPlainNodeReference(), "$that");
-                Expression receiver = fieldNode.isStatic()?new ClassExpression(node):new VariableExpression(param);
+                Expression receiver = fieldNode.isStatic() ? new ClassExpression(node) : new VariableExpression(param);
                 Statement stmt = new ExpressionStatement(new PropertyExpression(
                         receiver,
                         fieldNode.getName()
                 ));
-                MethodNode accessor = node.addMethod("pfaccess$"+acc, access, fieldNode.getOriginType(), new Parameter[]{param}, ClassNode.EMPTY_ARRAY, stmt);
-                privateConstantAccessors.put(fieldNode.getName(), accessor);
+                MethodNode accessor = node.addMethod("pfaccess$" + acc, access, fieldNode.getOriginType(), new Parameter[]{param}, ClassNode.EMPTY_ARRAY, stmt);
+                privateFieldAccessors.put(fieldNode.getName(), accessor);
+            }
+
+            if (generateMutator) {
+                //increment acc if it hasn't been incremented in the current iteration
+                if (!generateAccessor) acc++;
+                Parameter param = new Parameter(node.getPlainNodeReference(), "$that");
+                Expression receiver = fieldNode.isStatic() ? new ClassExpression(node) : new VariableExpression(param);
+                Parameter value = new Parameter(fieldNode.getOriginType(), "$value");
+                Statement stmt = GeneralUtils.assignS(
+                        new PropertyExpression(receiver, fieldNode.getName()),
+                        new VariableExpression(value)
+                );
+                MethodNode mutator = node.addMethod("pfaccess$0" + acc, access, fieldNode.getOriginType(), new Parameter[]{param, value}, ClassNode.EMPTY_ARRAY, stmt);
+                privateFieldMutators.put(fieldNode.getName(), mutator);
             }
         }
-        node.setNodeMetaData(PRIVATE_FIELDS_ACCESSORS, privateConstantAccessors);
+        if (privateFieldAccessors != null) node.setNodeMetaData(PRIVATE_FIELDS_ACCESSORS, privateFieldAccessors);
+        if (privateFieldMutators != null) node.setNodeMetaData(PRIVATE_FIELDS_MUTATORS, privateFieldMutators);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/groovy/blob/e1e60f61/src/main/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java b/src/main/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java
index 8b1d779..84e25eb 100644
--- a/src/main/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java
+++ b/src/main/org/codehaus/groovy/transform/sc/transformers/VariableExpressionTransformer.java
@@ -66,6 +66,9 @@ public class VariableExpressionTransformer {
 
     private Expression tryTransformPrivateFieldAccess(VariableExpression expr) {
         FieldNode field = expr.getNodeMetaData(StaticTypesMarker.PV_FIELDS_ACCESS);
+        if (field == null) {
+            field = expr.getNodeMetaData(StaticTypesMarker.PV_FIELDS_MUTATION);
+        }
         if (field != null) {
             // access to a private field from a section of code that normally doesn't have access to it, like a
             // closure or an inner class

http://git-wip-us.apache.org/repos/asf/groovy/blob/e1e60f61/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java b/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
index 19077e2..ef2e61f 100644
--- a/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
+++ b/src/main/org/codehaus/groovy/transform/stc/StaticTypeCheckingVisitor.java
@@ -342,13 +342,14 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
     }
 
     /**
-     * Given a field node, checks if we are calling a private field from an inner class.
+     * Given a field node, checks if we are accessing or setting a private field from an inner class.
      */
-    private void checkOrMarkPrivateAccess(Expression source, FieldNode fn) {
+    private void checkOrMarkPrivateAccess(Expression source, FieldNode fn, boolean lhsOfAssignment) {
         if (fn!=null && Modifier.isPrivate(fn.getModifiers()) &&
             (fn.getDeclaringClass() != typeCheckingContext.getEnclosingClassNode() || typeCheckingContext.getEnclosingClosure()!=null) &&
             fn.getDeclaringClass().getModule() == typeCheckingContext.getEnclosingClassNode().getModule()) {
-            addPrivateFieldOrMethodAccess(source, fn.getDeclaringClass(), StaticTypesMarker.PV_FIELDS_ACCESS, fn);
+            StaticTypesMarker marker = lhsOfAssignment ? StaticTypesMarker.PV_FIELDS_MUTATION : StaticTypesMarker.PV_FIELDS_ACCESS;
+            addPrivateFieldOrMethodAccess(source, fn.getDeclaringClass(), marker, fn);
         }
     }
 
@@ -995,6 +996,11 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
 
         if (!typeCheckMultipleAssignmentAndContinue(leftExpression, rightExpression)) return;
 
+        if (leftExpression instanceof VariableExpression
+                && ((VariableExpression) leftExpression).getAccessedVariable() instanceof FieldNode) {
+            checkOrMarkPrivateAccess(leftExpression, (FieldNode) ((VariableExpression) leftExpression).getAccessedVariable(), true);
+        }
+
         //TODO: need errors for write-only too!
         if (addedReadOnlyPropertyError(leftExpression)) return;
 
@@ -1218,7 +1224,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
 
                 FieldNode field = current.getDeclaredField(propertyName);
                 field = allowStaticAccessToMember(field, staticOnly);
-                if (storeField(field, isAttributeExpression, pexp, current, visitor, receiver.getData())) return true;
+                if (storeField(field, isAttributeExpression, pexp, current, visitor, receiver.getData(), !readMode)) return true;
 
                 PropertyNode propertyNode = current.getProperty(propertyName);
                 propertyNode = allowStaticAccessToMember(propertyNode, staticOnly);
@@ -1226,7 +1232,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
 
                 boolean isThisExpression = objectExpression instanceof VariableExpression &&
                         ((VariableExpression) objectExpression).isThisExpression() && objectExpressionType.equals(current);
-                if (storeField(field, isThisExpression, pexp, receiver.getType(), visitor, receiver.getData()))
+                if (storeField(field, isThisExpression, pexp, receiver.getType(), visitor, receiver.getData(), !readMode))
                     return true;
 
                 MethodNode getter = current.getGetterMethod("get" + capName);
@@ -1282,7 +1288,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
                 }
                 foundGetterOrSetter = foundGetterOrSetter || !setters.isEmpty() || getter != null;
 
-                if (storeField(field, true, pexp, current, visitor, receiver.getData())) return true;
+                if (storeField(field, true, pexp, current, visitor, receiver.getData(), !readMode)) return true;
                 // if the property expression is an attribute expression (o.@attr), then
                 // we stop now, otherwise we must check the parent class
                 if (/*!isAttributeExpression && */current.getSuperClass() != null) {
@@ -1439,11 +1445,11 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
         storeType(expressionToStoreOn, type);
     }
 
-    private boolean storeField(FieldNode field, boolean returnTrueIfFieldExists, PropertyExpression expressionToStoreOn, ClassNode receiver, ClassCodeVisitorSupport visitor, String delegationData) {
+    private boolean storeField(FieldNode field, boolean returnTrueIfFieldExists, PropertyExpression expressionToStoreOn, ClassNode receiver, ClassCodeVisitorSupport visitor, String delegationData, boolean lhsOfAssignment) {
         if (field==null || !returnTrueIfFieldExists) return false;
         if (visitor != null) visitor.visitField(field);
         storeWithResolve(field.getOriginType(), receiver, field.getDeclaringClass(), field.isStatic(), expressionToStoreOn);
-        checkOrMarkPrivateAccess(expressionToStoreOn, field);
+        checkOrMarkPrivateAccess(expressionToStoreOn, field, lhsOfAssignment);
         if (delegationData!=null) {
             expressionToStoreOn.putNodeMetaData(StaticTypesMarker.IMPLICIT_RECEIVER, delegationData);
         }
@@ -1460,7 +1466,6 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
         return true;
     }
 
-
     protected void storeInferredTypeForPropertyExpression(final PropertyExpression pexp, final ClassNode flatInferredType) {
         if (pexp.isSpreadSafe()) {
             ClassNode list = LIST_TYPE.getPlainNodeReference();
@@ -3952,7 +3957,7 @@ public class StaticTypeCheckingVisitor extends ClassCodeVisitorSupport {
             if (vexp == VariableExpression.SUPER_EXPRESSION) return makeSuper();
             final Variable variable = vexp.getAccessedVariable();
             if (variable instanceof FieldNode) {
-                checkOrMarkPrivateAccess(vexp, (FieldNode) variable);
+                checkOrMarkPrivateAccess(vexp, (FieldNode) variable, isLHSOfEnclosingAssignment(vexp));
                 return getType((FieldNode) variable);
             }
             if (variable != null && variable != vexp && variable instanceof VariableExpression) {

http://git-wip-us.apache.org/repos/asf/groovy/blob/e1e60f61/src/main/org/codehaus/groovy/transform/stc/StaticTypesMarker.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/transform/stc/StaticTypesMarker.java b/src/main/org/codehaus/groovy/transform/stc/StaticTypesMarker.java
index fb3abde..9785433 100644
--- a/src/main/org/codehaus/groovy/transform/stc/StaticTypesMarker.java
+++ b/src/main/org/codehaus/groovy/transform/stc/StaticTypesMarker.java
@@ -34,6 +34,7 @@ public enum StaticTypesMarker {
     DELEGATION_METADATA, // used to store the delegation strategy and delegate type of a closure when declared with @DelegatesTo
     IMPLICIT_RECEIVER, // if the receiver is implicit but not "this", store the name of the receiver (delegate or owner)
     PV_FIELDS_ACCESS, // set of private fields that are accessed from closures or inner classes
+    PV_FIELDS_MUTATION, // set of private fields that are set from closures or inner classes
     PV_METHODS_ACCESS, // set of private methods that are accessed from closures or inner classes
     DYNAMIC_RESOLUTION, // call recognized by a type checking extension as a dynamic method call
     SUPER_MOP_METHOD_REQUIRED // used to store the list of MOP methods that still have to be generated

http://git-wip-us.apache.org/repos/asf/groovy/blob/e1e60f61/src/test/org/codehaus/groovy/classgen/asm/sc/FieldsAndPropertiesStaticCompileTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/org/codehaus/groovy/classgen/asm/sc/FieldsAndPropertiesStaticCompileTest.groovy b/src/test/org/codehaus/groovy/classgen/asm/sc/FieldsAndPropertiesStaticCompileTest.groovy
index 61a0216..6af5f13 100644
--- a/src/test/org/codehaus/groovy/classgen/asm/sc/FieldsAndPropertiesStaticCompileTest.groovy
+++ b/src/test/org/codehaus/groovy/classgen/asm/sc/FieldsAndPropertiesStaticCompileTest.groovy
@@ -533,4 +533,167 @@ import org.codehaus.groovy.transform.sc.ListOfExpressionsExpression
             foo?.id = 'new'
         '''
     }
+
+    void testPrivateFieldMutationInClosureUsesBridgeMethod() {
+        try {
+            assertScript '''
+                class Foo {
+                    private String s
+                    Closure c = { this.s = 'abc' }
+
+                    void test() {
+                        c()
+                        assert s == 'abc'
+                    }
+                }
+                new Foo().test()
+            '''
+        } finally {
+            assert astTrees['Foo$_closure1'][1].contains('INVOKESTATIC Foo.pfaccess$00 (LFoo;Ljava/lang/String;)Ljava/lang/String')
+        }
+    }
+
+    void testImplicitPrivateFieldMutationInClosureUsesBridgeMethod() {
+        try {
+            assertScript '''
+                class Foo {
+                    private String s
+                    Closure c = { s = 'abc' }
+
+                    String test() {
+                        c()
+                        assert s == 'abc'
+                    }
+                }
+                new Foo().test()
+            '''
+        } finally {
+            assert astTrees['Foo$_closure1'][1].contains('INVOKESTATIC Foo.pfaccess$00 (LFoo;Ljava/lang/String;)Ljava/lang/String')
+        }
+    }
+
+    void testPrivateStaticFieldMutationInClosureUsesBridgeMethod() {
+        try {
+            assertScript '''
+                class Foo {
+                    private static String s
+                    Closure c = { s = 'abc' }
+
+                    String test() {
+                        c()
+                        assert s == 'abc'
+                    }
+                }
+                new Foo().test()
+            '''
+        } finally {
+            assert astTrees['Foo$_closure1'][1].contains('INVOKESTATIC Foo.pfaccess$00 (LFoo;Ljava/lang/String;)Ljava/lang/String')
+        }
+    }
+
+    void testPrivateFieldMutationInAICUsesBridgeMethod() {
+        try {
+            assertScript '''
+                class A {
+                    private int x
+                    void test() {
+                        def aic = new Runnable() { void run() { A.this.x = 666 } }
+                        aic.run()
+                        assert x == 666
+                    }
+                }
+                new A().test()
+            '''
+        } finally {
+            assert astTrees['A$1'][1].contains('INVOKESTATIC A.pfaccess$00 (LA;I)I')
+        }
+    }
+
+    void testImplicitPrivateFieldMutationInAICUsesBridgeMethod() {
+        try {
+            assertScript '''
+                class A {
+                    private int x
+                    void test() {
+                        def aic = new Runnable() { void run() { x = 666 } }
+                        aic.run()
+                        assert x == 666
+                    }
+                }
+                new A().test()
+            '''
+        } finally {
+            assert astTrees['A$1'][1].contains('INVOKESTATIC A.pfaccess$00 (LA;I)I')
+        }
+    }
+
+    void testPrivateStaticFieldMutationInAICUsesBridgeMethod() {
+        try {
+            assertScript '''
+                class A {
+                    private static int x
+                    void test() {
+                        def aic = new Runnable() { void run() { x = 666 } }
+                        aic.run()
+                        assert x == 666
+                    }
+                }
+                new A().test()
+            '''
+        } finally {
+            assert astTrees['A$1'][1].contains('INVOKESTATIC A.pfaccess$00 (LA;I)I')
+        }
+    }
+
+    void testMultiplePrivateFieldMutatorBridgeMethods() {
+        try {
+            assertScript '''
+                class A {
+                    private int x
+                    private String y
+                    Closure mutate = { x = 1; y = 'abc' }
+
+                    void test() {
+                        mutate()
+                        assert x == 1
+                        assert y == 'abc'
+                    }
+                }
+                new A().test()
+            '''
+        } finally {
+            assert astTrees['A$_closure1'][1].contains('INVOKESTATIC A.pfaccess$00 (LA;I)I')
+            assert astTrees['A$_closure1'][1].contains('INVOKESTATIC A.pfaccess$01 (LA;Ljava/lang/String;)Ljava/lang/String;')
+        }
+    }
+
+    void testPrivateFieldBridgeMethodsAreGeneratedAsNecessary() {
+        try {
+            assertScript '''
+                class A {
+                    private int accessed = 0
+                    private String mutated
+                    private String accessedAndMutated = ''
+                    Closure c = {
+                        println accessed
+                        mutated = 'abc'
+                        println accessedAndMutated
+                        accessedAndMutated = 'def'
+                    }
+
+                    void test() {
+                        c()
+                        assert mutated == 'abc'
+                        assert accessedAndMutated == 'def'
+                    }
+                }
+                new A().test()
+            '''
+        } finally {
+            assert !astTrees['A'][1].contains('pfaccess$00') // no mutator bridge method for 'accessed'
+            assert !astTrees['A'][1].contains('pfaccess$1') // no accessor bridge method for 'mutated'
+            assert astTrees['A$_closure1'][1].contains('INVOKESTATIC A.pfaccess$2 (LA;)Ljava/lang/String;')
+            assert astTrees['A$_closure1'][1].contains('INVOKESTATIC A.pfaccess$02 (LA;Ljava/lang/String;)Ljava/lang/String;')
+        }
+    }
 }