You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by bl...@apache.org on 2017/12/29 00:08:13 UTC

groovy git commit: GROOVY-8385: extend static compilation support for getting and setting fields. This commit does not cover attached blocks or inner classes for setting a field. Furthermore spreading is also not fully static compiled. The get field code

Repository: groovy
Updated Branches:
  refs/heads/master 6e5f47f26 -> 033b416a0


GROOVY-8385: extend static compilation support for getting and setting fields. This commit does not cover attached blocks or inner classes for setting a field. Furthermore spreading is also not fully static compiled. The get field code is absed of the property getter code and should also cover inner classes, attached blocks but also not spreading fully.


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

Branch: refs/heads/master
Commit: 033b416a0ec118d1c63fb13b4d0e71f93894338f
Parents: 6e5f47f
Author: Jochen Theodorou <bl...@gmx.org>
Authored: Fri Dec 29 00:52:40 2017 +0100
Committer: Jochen Theodorou <bl...@gmx.org>
Committed: Fri Dec 29 00:52:56 2017 +0100

----------------------------------------------------------------------
 .../groovy/classgen/AsmClassGenerator.java      | 90 ++++++++++++++------
 .../groovy/classgen/asm/CallSiteWriter.java     | 13 +++
 .../asm/sc/StaticTypesCallSiteWriter.java       | 81 ++++++++++++++++--
 .../asm/sc/StaticTypesStatementWriter.java      |  1 +
 .../groovy/control/CompilationUnit.java         |  5 ++
 .../FieldsAndPropertiesStaticCompileTest.groovy | 69 +++++++++++++++
 6 files changed, 226 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/033b416a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
index 208ab46..3d1b841 100644
--- a/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
+++ b/src/main/java/org/codehaus/groovy/classgen/AsmClassGenerator.java
@@ -110,7 +110,10 @@ import org.objectweb.asm.Label;
 import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
+import org.objectweb.asm.util.TraceMethodVisitor;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -122,11 +125,6 @@ import java.util.Map;
 /**
  * Generates Java class versions of Groovy classes using ASM.
  *
- * @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
- * @author <a href="mailto:b55r@sina.com">Bing Ran</a>
- * @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a>
- * @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a>
- * @author Alex Tkachman
  */
 public class AsmClassGenerator extends ClassGenerator {
 
@@ -135,9 +133,9 @@ public class AsmClassGenerator extends ClassGenerator {
     private final String sourceFile;
 
     // fields and properties
-    static final MethodCallerMultiAdapter setField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setField", false, false);
+    public static final MethodCallerMultiAdapter setField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setField", false, false);
     public static final MethodCallerMultiAdapter getField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getField", false, false);
-    static final MethodCallerMultiAdapter setGroovyObjectField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setGroovyObjectField", false, false);
+    public static final MethodCallerMultiAdapter setGroovyObjectField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setGroovyObjectField", false, false);
     public static final MethodCallerMultiAdapter getGroovyObjectField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getGroovyObjectField", false, false);
     static final MethodCallerMultiAdapter setFieldOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setFieldOnSuper", false, false);
     static final MethodCallerMultiAdapter getFieldOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getFieldOnSuper", false, false);
@@ -427,7 +425,24 @@ public class AsmClassGenerator extends ClassGenerator {
             try {
                 mv.visitMaxs(0, 0);
             } catch (Exception e) {
-                throw new GroovyRuntimeException("ASM reporting processing error for "+controller.getClassNode()+"#"+node.getName()+" with signature "+node.getTypeDescriptor()+" in "+sourceFile+":"+node.getLineNumber(), e);
+                StringWriter writer = null;
+                if (mv instanceof TraceMethodVisitor) {
+                    TraceMethodVisitor tracer = (TraceMethodVisitor) mv;
+                    writer = new StringWriter();
+                    PrintWriter p = new PrintWriter(writer);
+                    tracer.p.print(p);
+                    p.flush();
+                }
+                StringBuffer outBuffer = new StringBuffer();
+                outBuffer.append("ASM reporting processing error for ");
+                outBuffer.append(controller.getClassNode().toString()+"#"+node.getName());
+                outBuffer.append(" with signature "+node.getTypeDescriptor());
+                outBuffer.append(" in "+sourceFile+":"+node.getLineNumber());
+                if (writer != null) {
+                    outBuffer.append("\nLast known generated bytecode in last generated method or constructor:\n");
+                    outBuffer.append(writer);
+                }
+                throw new GroovyRuntimeException(outBuffer.toString(), e);
             }
         }
         mv.visitEnd();
@@ -890,14 +905,38 @@ public class AsmClassGenerator extends ClassGenerator {
         return name;
     }
 
-    private FieldNode getDeclaredFieldOfCurrentClassOrAccessibleFieldOfSuper(ClassNode current, String name, boolean skipCurrent) {
+    public static boolean samePackages(final String pkg1, final String pkg2) {
+        return (
+                (pkg1 ==null && pkg2 ==null)
+                        || pkg1 !=null && pkg1.equals(pkg2)
+        );
+    }
+
+    private static boolean isValidFieldNodeForByteCodeAccess(FieldNode fn, ClassNode accessingNode) {
+        if (fn == null) return false;
+        ClassNode declaringClass = fn.getDeclaringClass();
+        // same class is always allowed access
+        if (Modifier.isPublic(fn.getModifiers()) || declaringClass.equals(accessingNode)) return true;
+        boolean samePackages = samePackages(declaringClass.getPackageName(), accessingNode.getPackageName());
+        // protected means same class or same package, or subclass
+        if (Modifier.isProtected(fn.getModifiers()) && (samePackages || accessingNode.isDerivedFrom(declaringClass))) {
+            return true;
+        }
+        if (!Modifier.isPrivate(fn.getModifiers())) {
+            // package private is the only modifier left. It means  same package is allowed, subclass not, same class is
+            return samePackages;
+        }
+        return false;
+    }
+
+    public static FieldNode getDeclaredFieldOfCurrentClassOrAccessibleFieldOfSuper(ClassNode accessingNode, ClassNode current, String name, boolean skipCurrent) {
         if (!skipCurrent) {
             FieldNode currentClassField = current.getDeclaredField(name);
-            if (currentClassField != null) return currentClassField;
+            if (isValidFieldNodeForByteCodeAccess(currentClassField, accessingNode)) return currentClassField;
         }
         for (ClassNode node = current.getSuperClass(); node!=null; node = node.getSuperClass()) {
             FieldNode fn = node.getDeclaredField(name);
-            if (fn != null && (fn.isPublic() || fn.isProtected())) return fn;
+            if (isValidFieldNodeForByteCodeAccess(fn, accessingNode)) return fn;
         }
         return null;
     }
@@ -1028,20 +1067,17 @@ public class AsmClassGenerator extends ClassGenerator {
             return;
         }
 
-        if (adapter == getProperty && !expression.isSpreadSafe() && propName != null) {
-            controller.getCallSiteWriter().makeGetPropertySite(objectExpression, propName, expression.isSafe(), expression.isImplicitThis());
-        } else if (adapter == getGroovyObjectProperty && !expression.isSpreadSafe() && propName != null) {
-            controller.getCallSiteWriter().makeGroovyObjectGetPropertySite(objectExpression, propName, expression.isSafe(), expression.isImplicitThis());
+        if (propName!=null) {
+            // TODO: spread safe should be handled inside
+            if (adapter == getProperty && !expression.isSpreadSafe()) {
+                controller.getCallSiteWriter().makeGetPropertySite(objectExpression, propName, expression.isSafe(), expression.isImplicitThis());
+            } else if (adapter == getGroovyObjectProperty && !expression.isSpreadSafe()) {
+                controller.getCallSiteWriter().makeGroovyObjectGetPropertySite(objectExpression, propName, expression.isSafe(), expression.isImplicitThis());
+            } else {
+                controller.getCallSiteWriter().fallbackAttributeOrPropertySite(expression, objectExpression, propName, adapter);
+            }
         } else {
-            // todo: for improved modularity and extensibility, this should be moved into a writer
-            if (controller.getCompileStack().isLHS()) controller.getOperandStack().box();
-            controller.getInvocationWriter().makeCall(
-                    expression,
-                    objectExpression, // receiver
-                    new CastExpression(ClassHelper.STRING_TYPE, expression.getProperty()), // messageName
-                    MethodCallExpression.NO_ARGUMENTS, adapter,
-                    expression.isSafe(), expression.isSpreadSafe(), expression.isImplicitThis()
-            );
+            controller.getCallSiteWriter().fallbackAttributeOrPropertySite(expression, objectExpression, null, adapter);
         }
     }
 
@@ -1085,9 +1121,11 @@ public class AsmClassGenerator extends ClassGenerator {
             // let's use the field expression if it's available
             String name = expression.getPropertyAsString();
             if (name != null) {
-                FieldNode field = getDeclaredFieldOfCurrentClassOrAccessibleFieldOfSuper(classNode, name, isSuperExpression(objectExpression));
+                FieldNode field = getDeclaredFieldOfCurrentClassOrAccessibleFieldOfSuper(classNode, classNode, name, isSuperExpression(objectExpression));
                 if (field != null) {
-                    visitFieldExpression(new FieldExpression(field));
+                    FieldExpression exp = new FieldExpression(field);
+                    exp.setSourcePosition(expression);
+                    visitFieldExpression(exp);
                     return;
                 }
             }

http://git-wip-us.apache.org/repos/asf/groovy/blob/033b416a/src/main/java/org/codehaus/groovy/classgen/asm/CallSiteWriter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/CallSiteWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/CallSiteWriter.java
index 716f97e..3e3f0e1 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/CallSiteWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/CallSiteWriter.java
@@ -25,6 +25,8 @@ import org.codehaus.groovy.ast.expr.ArgumentListExpression;
 import org.codehaus.groovy.ast.expr.CastExpression;
 import org.codehaus.groovy.ast.expr.ClassExpression;
 import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.expr.MethodCallExpression;
+import org.codehaus.groovy.ast.expr.PropertyExpression;
 import org.codehaus.groovy.ast.expr.TupleExpression;
 import org.codehaus.groovy.classgen.AsmClassGenerator;
 import org.codehaus.groovy.runtime.callsite.CallSite;
@@ -384,4 +386,15 @@ public class CallSiteWriter {
     public boolean hasCallSiteUse() {
         return callSiteArrayVarIndex>=0;
     }
+
+    public void fallbackAttributeOrPropertySite(PropertyExpression expression, Expression objectExpression, String name, MethodCallerMultiAdapter adapter) {
+        if (controller.getCompileStack().isLHS()) controller.getOperandStack().box();
+        controller.getInvocationWriter().makeCall(
+                expression,
+                objectExpression, // receiver
+                new CastExpression(ClassHelper.STRING_TYPE, expression.getProperty()), // messageName
+                MethodCallExpression.NO_ARGUMENTS, adapter,
+                expression.isSafe(), expression.isSpreadSafe(), expression.isImplicitThis()
+        );
+    }
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/033b416a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java
index 98ad092..28b8aab 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesCallSiteWriter.java
@@ -36,10 +36,12 @@ import org.codehaus.groovy.ast.expr.MethodCallExpression;
 import org.codehaus.groovy.ast.expr.PropertyExpression;
 import org.codehaus.groovy.ast.expr.VariableExpression;
 import org.codehaus.groovy.ast.stmt.EmptyStatement;
+import org.codehaus.groovy.classgen.AsmClassGenerator;
 import org.codehaus.groovy.classgen.BytecodeExpression;
 import org.codehaus.groovy.classgen.asm.BytecodeHelper;
 import org.codehaus.groovy.classgen.asm.CallSiteWriter;
 import org.codehaus.groovy.classgen.asm.CompileStack;
+import org.codehaus.groovy.classgen.asm.MethodCallerMultiAdapter;
 import org.codehaus.groovy.classgen.asm.OperandStack;
 import org.codehaus.groovy.classgen.asm.TypeChooser;
 import org.codehaus.groovy.runtime.InvokerHelper;
@@ -79,6 +81,7 @@ import static org.codehaus.groovy.ast.ClassHelper.getWrapper;
 import static org.codehaus.groovy.ast.ClassHelper.int_TYPE;
 import static org.codehaus.groovy.ast.ClassHelper.isPrimitiveType;
 import static org.codehaus.groovy.ast.ClassHelper.make;
+import static org.codehaus.groovy.classgen.AsmClassGenerator.samePackages;
 import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.chooseBestMethod;
 import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.findDGMMethodsByNameAndArguments;
 import static org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport.implementsInterfaceOrIsSubclassOf;
@@ -630,13 +633,6 @@ public class StaticTypesCallSiteWriter extends CallSiteWriter implements Opcodes
         return false;
     }
 
-    private static boolean samePackages(final String pkg1, final String pkg2) {
-        return (
-                (pkg1 ==null && pkg2 ==null)
-                || pkg1 !=null && pkg1.equals(pkg2)
-                );
-    }
-
     private static boolean isDirectAccessAllowed(FieldNode a, ClassNode receiver, boolean isSamePackage) {
         ClassNode declaringClass = a.getDeclaringClass().redirect();
         ClassNode receiverType = receiver.redirect();
@@ -884,5 +880,76 @@ public class StaticTypesCallSiteWriter extends CallSiteWriter implements Opcodes
         controller.getOperandStack().replace(Number_TYPE, m2 - m1);
     }
 
+    @Override
+    public void fallbackAttributeOrPropertySite(PropertyExpression expression, Expression objectExpression, String name, MethodCallerMultiAdapter adapter) {
+        if (name!=null &&
+            (adapter == AsmClassGenerator.setField || adapter == AsmClassGenerator.setGroovyObjectField)
+        ) {
+            TypeChooser typeChooser = controller.getTypeChooser();
+            ClassNode classNode = controller.getClassNode();
+            ClassNode rType = typeChooser.resolveType(objectExpression, classNode);
+            if (controller.getCompileStack().isLHS()) {
+                if (setField(expression, objectExpression, rType, name)) return;
+            } else {
+                if (getField(expression, objectExpression, rType, name)) return;
+            }
+        }
+        super.fallbackAttributeOrPropertySite(expression, objectExpression, name, adapter);
+    }
+
+    // this is just a simple set field handling static and non-static, but not Closure and inner classes
+    private boolean setField(PropertyExpression expression, Expression objectExpression, ClassNode rType, String name) {
+        if (expression.isSafe()) return false;
+        FieldNode fn = AsmClassGenerator.getDeclaredFieldOfCurrentClassOrAccessibleFieldOfSuper(controller.getClassNode(), rType, name, false);
+        if (fn==null) return false;
+        OperandStack stack = controller.getOperandStack();
+        stack.doGroovyCast(fn.getType());
+
+        MethodVisitor mv = controller.getMethodVisitor();
+        String ownerName = BytecodeHelper.getClassInternalName(fn.getOwner());
+        if (!fn.isStatic()) {
+            controller.getCompileStack().pushLHS(false);
+            objectExpression.visit(controller.getAcg());
+            controller.getCompileStack().popLHS();
+            if (!rType.equals(stack.getTopOperand())) {
+                BytecodeHelper.doCast(mv, rType);
+                stack.replace(rType);
+            }
+            stack.swap();
+            mv.visitFieldInsn(PUTFIELD, ownerName, name, BytecodeHelper.getTypeDescription(fn.getType()));
+            stack.remove(1);
+        } else {
+            mv.visitFieldInsn(PUTSTATIC, ownerName, name, BytecodeHelper.getTypeDescription(fn.getType()));
+        }
+
+        //mv.visitInsn(ACONST_NULL);
+        //stack.replace(OBJECT_TYPE);
+        return true;
+    }
+
+    private boolean getField(PropertyExpression expression, Expression receiver, ClassNode receiverType, String name) {
+        ClassNode classNode = controller.getClassNode();
+        boolean safe = expression.isSafe();
+        boolean implicitThis = expression.isImplicitThis();
+
+        if (makeGetField(receiver, receiverType, name, safe, implicitThis, samePackages(receiverType.getPackageName(), classNode.getPackageName()))) return true;
+        if (receiver instanceof ClassExpression) {
+            if (makeGetField(receiver, receiver.getType(), name, safe, implicitThis, samePackages(receiver.getType().getPackageName(), classNode.getPackageName()))) return true;
+            if (makeGetPrivateFieldWithBridgeMethod(receiver, receiver.getType(), name, safe, implicitThis)) return true;
+        }
+        if (makeGetPrivateFieldWithBridgeMethod(receiver, receiverType, name, safe, implicitThis)) return true;
 
+        boolean isClassReceiver = false;
+        if (isClassClassNodeWrappingConcreteType(receiverType)) {
+            isClassReceiver = true;
+            receiverType = receiverType.getGenericsTypes()[0].getType();
+        }
+        if (isClassReceiver && makeGetField(receiver, CLASS_Type, name, safe, false, true)) return true;
+        if (receiverType.isEnum()) {
+            controller.getMethodVisitor().visitFieldInsn(GETSTATIC, BytecodeHelper.getClassInternalName(receiverType), name, BytecodeHelper.getTypeDescription(receiverType));
+            controller.getOperandStack().push(receiverType);
+            return true;
+        }
+        return false;
+    }
 }

http://git-wip-us.apache.org/repos/asf/groovy/blob/033b416a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java
index cabc3c4..c27ae5f 100644
--- a/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java
+++ b/src/main/java/org/codehaus/groovy/classgen/asm/sc/StaticTypesStatementWriter.java
@@ -27,6 +27,7 @@ import org.codehaus.groovy.ast.expr.MethodCallExpression;
 import org.codehaus.groovy.ast.stmt.BlockStatement;
 import org.codehaus.groovy.ast.stmt.ForStatement;
 import org.codehaus.groovy.classgen.AsmClassGenerator;
+import org.codehaus.groovy.classgen.asm.BytecodeHelper;
 import org.codehaus.groovy.classgen.asm.BytecodeVariable;
 import org.codehaus.groovy.classgen.asm.CompileStack;
 import org.codehaus.groovy.classgen.asm.MethodCaller;

http://git-wip-us.apache.org/repos/asf/groovy/blob/033b416a/src/main/java/org/codehaus/groovy/control/CompilationUnit.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/control/CompilationUnit.java b/src/main/java/org/codehaus/groovy/control/CompilationUnit.java
index dab0e55..a766784 100644
--- a/src/main/java/org/codehaus/groovy/control/CompilationUnit.java
+++ b/src/main/java/org/codehaus/groovy/control/CompilationUnit.java
@@ -52,11 +52,14 @@ import org.codehaus.groovy.transform.sc.StaticCompilationMetadataKeys;
 import org.codehaus.groovy.transform.trait.TraitComposer;
 import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.util.Textifier;
+import org.objectweb.asm.util.TraceClassVisitor;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PrintWriter;
 import java.net.URL;
 import java.security.CodeSource;
 import java.util.ArrayList;
@@ -840,6 +843,8 @@ public class CompilationUnit extends ProcessingUnit {
             // also takes care of both \ and / depending on the host compiling environment
             if (sourceName != null)
                 sourceName = sourceName.substring(Math.max(sourceName.lastIndexOf('\\'), sourceName.lastIndexOf('/')) + 1);
+            //TraceClassVisitor tracer = new TraceClassVisitor(visitor, new PrintWriter(System.err,true));
+            //AsmClassGenerator generator = new AsmClassGenerator(source, context, tracer, sourceName);
             AsmClassGenerator generator = new AsmClassGenerator(source, context, visitor, sourceName);
 
             //

http://git-wip-us.apache.org/repos/asf/groovy/blob/033b416a/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 eef6232..4bef2c4 100644
--- a/src/test/org/codehaus/groovy/classgen/asm/sc/FieldsAndPropertiesStaticCompileTest.groovy
+++ b/src/test/org/codehaus/groovy/classgen/asm/sc/FieldsAndPropertiesStaticCompileTest.groovy
@@ -185,6 +185,75 @@ class FieldsAndPropertiesStaticCompileTest extends FieldsAndPropertiesSTCTest im
         assert astTrees['B'][1].contains('INVOKEVIRTUAL B.setX')
     }
 
+    void testUseDirectWriteFieldAccessFromOutsideClass() {
+        assertScript '''
+            class A {
+                public int x
+            }
+            class B  {
+                void directAccess(A a) {
+                    a.@x = 2
+                }
+            }
+            B b = new B()
+            A a = new A()
+            b.directAccess(a)
+            assert a.x == 2
+        '''
+        assert astTrees['B'][1].contains('PUTFIELD A.x')
+    }
+
+    void testUseDirectWriteFieldAccessPrivateWithRuntimeClassBeingDifferent() {
+        assertScript '''
+            class A {
+                private int x
+                public A(int x) {
+                    this.@x = x
+                }
+                public boolean sameAs(A a) {
+                    return this.@x == a.@x
+                }
+            }
+            class B extends A {
+                // B.x visible in B A.x in A, but reflection depending on the runtime type
+                // would see B.x in A#sameAs and not A.x
+                private int x 
+                public B(int x) {
+                    super(x)
+                    this.@x = x + 50
+                }
+            }
+            B b = new B(1)
+            A a = new A(1)
+            assert b.sameAs(a)
+        '''
+        // same with property style access:
+        assertScript '''
+            class A {
+                private int x
+                public A(int x) {
+                    this.x = x
+                }
+                public boolean sameAs(A a) {
+                    return this.x == a.x
+                }
+            }
+            class B extends A {
+                // B.x visible in B A.x in A, but reflection depending on the runtime type
+                // would see B.x in A#sameAs and not A.x
+                private int x 
+                public B(int x) {
+                    super(x)
+                    this.x = x + 50
+                }
+            }
+            B b = new B(1)
+            A a = new A(1)
+            assert b.sameAs(a)
+        '''
+
+    }
+
     void testDirectReadFieldFromSameClass() {
         assertScript '''
             class A {