From c11f714a3eac0563bf6760dc9598d4b4781798a4 Mon Sep 17 00:00:00 2001
From: Adam Welc <adam.welc@oracle.com>
Date: Wed, 4 Nov 2015 14:15:29 -0800
Subject: [PATCH] Interning of ReadVariableNode ids and RSymbol names to make
 sure that string comparisons work correctly.

---
 .../fficall/jni/src/rffiutils.c                |  2 +-
 .../truffle/r/nodes/builtin/base/AsVector.java |  4 +++-
 .../r/nodes/builtin/base/FrameFunctions.java   | 11 ++++++++---
 .../truffle/r/nodes/builtin/base/Slot.java     |  4 +++-
 .../builtin/base/foreign/ForeignFunctions.java |  2 +-
 .../com/oracle/truffle/r/nodes/RASTUtils.java  | 15 ++++++++++-----
 .../truffle/r/nodes/access/AccessSlotNode.java |  8 ++------
 .../truffle/r/nodes/access/UpdateSlotNode.java |  7 ++++---
 .../access/WriteVariableNodeSyntaxHelper.java  |  5 ++++-
 .../access/variables/ReadVariableNode.java     |  6 ++++--
 .../truffle/r/nodes/unary/CastSymbolNode.java  |  7 +++++--
 .../oracle/truffle/r/parser/ast/Constant.java  | 18 +++++++++++++++++-
 .../truffle/r/parser/ast/FieldAccess.java      |  5 +----
 .../oracle/truffle/r/runtime/RSerialize.java   |  6 ++++--
 .../truffle/r/runtime/data/RDataFactory.java   |  8 ++++++++
 .../com/oracle/truffle/r/runtime/ffi/DLL.java  |  4 +++-
 .../com/oracle/truffle/r/test/S4/TestS4.java   |  2 ++
 17 files changed, 80 insertions(+), 34 deletions(-)

diff --git a/com.oracle.truffle.r.native/fficall/jni/src/rffiutils.c b/com.oracle.truffle.r.native/fficall/jni/src/rffiutils.c
index 346ba55cf2..a3e08bb2f2 100644
--- a/com.oracle.truffle.r.native/fficall/jni/src/rffiutils.c
+++ b/com.oracle.truffle.r.native/fficall/jni/src/rffiutils.c
@@ -73,7 +73,7 @@ void init_utils(JNIEnv *env) {
 	RRuntimeClass = checkFindClass(env, "com/oracle/truffle/r/runtime/RRuntime");
 	RInternalErrorClass = checkFindClass(env, "com/oracle/truffle/r/runtime/RInternalError");
 	unimplementedMethodID = checkGetMethodID(env, RInternalErrorClass, "unimplemented", "(Ljava/lang/String;)Ljava/lang/RuntimeException;", 1);
-	createSymbolMethodID = checkGetMethodID(env, RDataFactoryClass, "createSymbol", "(Ljava/lang/String;)Lcom/oracle/truffle/r/runtime/data/RSymbol;", 1);
+	createSymbolMethodID = checkGetMethodID(env, RDataFactoryClass, "createSymbolInterned", "(Ljava/lang/String;)Lcom/oracle/truffle/r/runtime/data/RSymbol;", 1);
     validateMethodID = checkGetMethodID(env, CallRFFIHelperClass, "validate", "(Ljava/lang/Object;)Ljava/lang/Object;", 1);
     for (int i = 0; i < CACHED_GLOBALREFS_TABLE_SIZE; i++) {
     	cachedGlobalRefs[i] = NULL;
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/AsVector.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/AsVector.java
index fc335525ef..1b79ed7583 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/AsVector.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/AsVector.java
@@ -123,7 +123,9 @@ public abstract class AsVector extends RBuiltinNode {
     @Specialization(guards = "isSymbol(x, mode)")
     protected RSymbol asVectorSymbol(RSymbol x, @SuppressWarnings("unused") String mode) {
         controlVisibility();
-        return RDataFactory.createSymbol(x.getName());
+        String sName = x.getName();
+        assert sName == sName.intern();
+        return RDataFactory.createSymbol(sName);
     }
 
     protected boolean isSymbol(@SuppressWarnings("unused") RSymbol x, String mode) {
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/FrameFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/FrameFunctions.java
index ed3f626254..a563a19e6f 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/FrameFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/FrameFunctions.java
@@ -26,6 +26,7 @@ import static com.oracle.truffle.r.runtime.RBuiltinKind.*;
 
 import java.util.*;
 
+import com.oracle.truffle.api.CompilerAsserts;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.dsl.*;
 import com.oracle.truffle.api.frame.*;
@@ -243,7 +244,9 @@ public class FrameFunctions {
                             if (n instanceof ConstantNode) {
                                 listValue = ((ConstantNode) n).getValue();
                             } else if (n instanceof ReadVariableNode) {
-                                listValue = RDataFactory.createSymbol(((ReadVariableNode) n).getIdentifier());
+                                String id = ((ReadVariableNode) n).getIdentifier();
+                                assert id == id.intern();
+                                listValue = RDataFactory.createSymbol(id);
                             } else if (n instanceof VarArgNode) {
                                 listValue = createVarArgSymbol((VarArgNode) n);
                             } else {
@@ -251,7 +254,7 @@ public class FrameFunctions {
                             }
                             pl.setCar(listValue);
                             if (varArgSignature.getName(i2) != null) {
-                                pl.setTag(RDataFactory.createSymbol(varArgSignature.getName(i2)));
+                                pl.setTag(RDataFactory.createSymbol(varArgSignature.getName(i2).intern()));
                             }
                             if (prev != null) {
                                 prev.setCdr(pl);
@@ -308,7 +311,9 @@ public class FrameFunctions {
 
         private static RSymbol createVarArgSymbol(VarArgNode varArgNode) {
             int vn = varArgNode.getIndex() + 1;
-            return RDataFactory.createSymbol((vn < 10 ? ".." : ".") + vn);
+            CompilerAsserts.neverPartOfCompilation(); // for string concatenation and interning
+            String varArgSymbol = (vn < 10 ? ".." : ".") + vn;
+            return RDataFactory.createSymbol(varArgSymbol.intern());
         }
 
         @Specialization
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Slot.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Slot.java
index 09c47e6c6c..aafa26a098 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Slot.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/Slot.java
@@ -58,7 +58,9 @@ public abstract class Slot extends RBuiltinNode {
 
     @Specialization
     protected Object getSlot(RAttributable object, Object nameObj) {
-        return accessSlotNode.executeAccess(object, getName(nameObj));
+        String name = getName(nameObj);
+        assert name == name.intern();
+        return accessSlotNode.executeAccess(object, name);
     }
 
 }
diff --git a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/ForeignFunctions.java b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/ForeignFunctions.java
index 208e4cb7ff..021eb25bbf 100644
--- a/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/ForeignFunctions.java
+++ b/com.oracle.truffle.r.nodes.builtin/src/com/oracle/truffle/r/nodes/builtin/base/foreign/ForeignFunctions.java
@@ -84,7 +84,7 @@ public class ForeignFunctions {
         Object list = RNull.instance;
         for (int i = args.getLength() - 1; i >= 0; i--) {
             String name = args.getSignature().getName(i);
-            list = RDataFactory.createPairList(args.getArgument(i), list, name == null ? RNull.instance : RDataFactory.createSymbol(name));
+            list = RDataFactory.createPairList(args.getArgument(i), list, name == null ? RNull.instance : RDataFactory.createSymbol(name.intern()));
         }
         list = RDataFactory.createPairList(symbolName, list);
         return list;
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RASTUtils.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RASTUtils.java
index 5cf1c1e345..43a00cc2b8 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RASTUtils.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/RASTUtils.java
@@ -22,6 +22,7 @@
  */
 package com.oracle.truffle.r.nodes;
 
+import com.oracle.truffle.api.CompilerAsserts;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.frame.*;
 import com.oracle.truffle.api.instrument.WrapperNode;
@@ -114,9 +115,11 @@ public class RASTUtils {
     public static RSymbol createRSymbol(Node readVariableNode) {
         if (readVariableNode instanceof ReadVariadicComponentNode) {
             ReadVariadicComponentNode rvcn = (ReadVariadicComponentNode) readVariableNode;
-            return RDataFactory.createSymbol(rvcn.getPrintForm());
+            return RDataFactory.createSymbol(rvcn.getPrintForm().intern());
         } else {
-            return RDataFactory.createSymbol(((ReadVariableNode) readVariableNode).getIdentifier());
+            String id = ((ReadVariableNode) readVariableNode).getIdentifier();
+            assert id == id.intern();
+            return RDataFactory.createSymbol(id);
         }
     }
 
@@ -229,25 +232,27 @@ public class RASTUtils {
      * or {@link GroupDispatchNode}.
      */
     public static Object findFunctionName(Node node) {
+        CompilerAsserts.neverPartOfCompilation(); // for string interning
         RNode child = (RNode) unwrap(getFunctionNode(node));
         if (child instanceof ConstantNode && ConstantNode.isFunction(child)) {
             return ((ConstantNode) child).getValue();
         } else if (child instanceof ReadVariableNode) {
             String name = ((ReadVariableNode) child).getIdentifier();
+            assert name == name.intern();
             return RDataFactory.createSymbol(name);
         } else if (child instanceof GroupDispatchNode) {
             GroupDispatchNode groupDispatchNode = (GroupDispatchNode) child;
             String gname = groupDispatchNode.getGenericName();
-            return RDataFactory.createSymbol(gname);
+            return RDataFactory.createSymbol(gname.intern());
         } else if (child instanceof RBuiltinNode) {
             RBuiltinNode builtinNode = (RBuiltinNode) child;
-            return RDataFactory.createSymbol((builtinNode.getBuiltin().getName()));
+            return RDataFactory.createSymbol((builtinNode.getBuiltin().getName().intern()));
         } else {
             // TODO This should really fail in some way as (clearly) this is not a "name"
             // some more complicated expression, just deparse it
             RDeparse.State state = RDeparse.State.createPrintableState();
             child.deparse(state);
-            return RDataFactory.createSymbol(state.toString());
+            return RDataFactory.createSymbol(state.toString().intern());
         }
     }
 
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/AccessSlotNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/AccessSlotNode.java
index f709f8d3b7..2b23220db5 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/AccessSlotNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/AccessSlotNode.java
@@ -41,7 +41,6 @@ public abstract class AccessSlotNode extends RNode {
     @Child private ClassHierarchyNode classHierarchy;
     @Child private TypeofNode typeofNode;
     private final BranchProfile noSlot = BranchProfile.create();
-    private final ConditionProfile nullSlot = ConditionProfile.createBinaryProfile();
 
     protected AttributeAccess createAttrAccess(String name) {
         return AttributeAccessNodeGen.create(name);
@@ -50,11 +49,8 @@ public abstract class AccessSlotNode extends RNode {
     private Object getSlotS4Internal(RAttributable object, String name, Object value) {
         if (value == null) {
             noSlot.enter();
+            assert name == name.intern();
             if (name == RRuntime.DOT_S3_CLASS) {
-                // TODO: this will not work if `@` function is called directly, as in:
-                // `@`(x, ".S3Class")
-                // in general, treatment of the name parameter has to be finessed to be
-                // fully compatible with GNU R on direct calls to `@` function
                 if (classHierarchy == null) {
                     CompilerDirectives.transferToInterpreterAndInvalidate();
                     classHierarchy = insert(ClassHierarchyNodeGen.create(true));
@@ -128,7 +124,7 @@ public abstract class AccessSlotNode extends RNode {
     }
 
     protected boolean isDotData(String name) {
-        // see comment on usinq object equality in getSlotS4()
+        assert name == name.intern();
         return name == RRuntime.DOT_DATA;
     }
 }
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/UpdateSlotNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/UpdateSlotNode.java
index 50d8164135..bdda2e770a 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/UpdateSlotNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/UpdateSlotNode.java
@@ -17,7 +17,6 @@ import com.oracle.truffle.api.dsl.*;
 import com.oracle.truffle.r.nodes.attributes.PutAttributeNode;
 import com.oracle.truffle.r.nodes.attributes.PutAttributeNodeGen;
 import com.oracle.truffle.r.nodes.function.ClassHierarchyNode;
-import com.oracle.truffle.r.runtime.*;
 import com.oracle.truffle.r.runtime.data.*;
 import com.oracle.truffle.r.runtime.data.model.*;
 import com.oracle.truffle.r.runtime.nodes.*;
@@ -44,7 +43,8 @@ public abstract class UpdateSlotNode extends RNode {
 
     @Specialization(contains = "updateSlotS4Cached")
     protected Object updateSlotS4(RS4Object object, String name, Object value) {
-        object.setAttr(name.intern(), value);
+        assert name == name.intern();
+        object.setAttr(name, value);
         return object;
     }
 
@@ -57,7 +57,8 @@ public abstract class UpdateSlotNode extends RNode {
 
     @Specialization(contains = "updateSlotCached")
     protected Object updateSlot(RAbstractContainer object, String name, Object value) {
-        object.setAttr(name.intern(), value);
+        assert name == name.intern();
+        object.setAttr(name, value);
         return object;
     }
 
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/WriteVariableNodeSyntaxHelper.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/WriteVariableNodeSyntaxHelper.java
index 96ed55de7d..2809aeba1c 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/WriteVariableNodeSyntaxHelper.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/WriteVariableNodeSyntaxHelper.java
@@ -22,6 +22,7 @@
  */
 package com.oracle.truffle.r.nodes.access;
 
+import com.oracle.truffle.api.CompilerAsserts;
 import com.oracle.truffle.r.nodes.RASTUtils;
 import com.oracle.truffle.r.runtime.*;
 import com.oracle.truffle.r.runtime.data.RDataFactory;
@@ -56,9 +57,11 @@ abstract class WriteVariableNodeSyntaxHelper extends WriteVariableNode {
     protected Object getRelementHelper(String op, int index) {
         switch (index) {
             case 0:
+                assert op == op.intern();
                 return RDataFactory.createSymbol(op);
             case 1:
-                return RDataFactory.createSymbol(getName().toString());
+                CompilerAsserts.neverPartOfCompilation();
+                return RDataFactory.createSymbol(getName().toString().intern());
             case 2:
                 return RASTUtils.createLanguageElement(getRhs());
             default:
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/ReadVariableNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/ReadVariableNode.java
index 673ac2dd79..329e4ca35f 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/ReadVariableNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/access/variables/ReadVariableNode.java
@@ -137,7 +137,7 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
 
     private ReadVariableNode(Object identifier, RType mode, ReadKind kind, boolean visibilityChange) {
         this.identifier = identifier;
-        this.identifierAsString = identifier.toString();
+        this.identifierAsString = identifier.toString().intern();
         this.mode = mode;
         this.kind = kind;
         this.visibilityChange = visibilityChange;
@@ -180,7 +180,9 @@ public final class ReadVariableNode extends RNode implements RSyntaxNode, Visibi
 
     @Override
     public Object getRelementImpl(int index) {
-        return RDataFactory.createSymbol(identifier.toString());
+        String id = identifier.toString();
+        assert id == id.intern();
+        return RDataFactory.createSymbol(id);
     }
 
     @Override
diff --git a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/CastSymbolNode.java b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/CastSymbolNode.java
index 4b00e38b6a..e90f8a8de9 100644
--- a/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/CastSymbolNode.java
+++ b/com.oracle.truffle.r.nodes/src/com/oracle/truffle/r/nodes/unary/CastSymbolNode.java
@@ -57,9 +57,11 @@ public abstract class CastSymbolNode extends CastBaseNode {
         return backQuote(toString(value));
     }
 
+    @TruffleBoundary
     @Specialization
     protected RSymbol doString(String value) {
-        return RDataFactory.createSymbol(value);
+        // TODO: see if this is going to hit us performance-wise
+        return RDataFactory.createSymbol(value.intern());
     }
 
     @Specialization
@@ -85,7 +87,8 @@ public abstract class CastSymbolNode extends CastBaseNode {
 
     @TruffleBoundary
     private static RSymbol backQuote(String s) {
-        return RDataFactory.createSymbol("`" + s + "`");
+        String quotedString = "`" + s + "`";
+        return RDataFactory.createSymbol(quotedString.intern());
     }
 
     public static CastSymbolNode createNonPreserving() {
diff --git a/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/ast/Constant.java b/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/ast/Constant.java
index 40f40c1a88..6177da34e1 100644
--- a/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/ast/Constant.java
+++ b/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/ast/Constant.java
@@ -30,8 +30,23 @@ public final class Constant extends ASTNode {
     private final String[] values;
     private final ConstantType type;
 
+    /*
+     * Used in case parameter is a single String value that should not be interned (e.g., NA)
+     */
+    private Constant(String value, SourceSection source) {
+        super(source);
+        this.values = new String[]{value};
+        this.type = ConstantType.STRING;
+    }
+
     private Constant(String[] values, ConstantType type, SourceSection source) {
         super(source);
+        if (type == ConstantType.STRING) {
+            assert values != null;
+            for (int i = 0; i < values.length; i++) {
+                values[i] = values[i].intern();
+            }
+        }
         this.values = values;
         this.type = type;
     }
@@ -95,7 +110,8 @@ public final class Constant extends ASTNode {
     }
 
     public static Constant createStringNA(SourceSection src) {
-        return new Constant(new String[]{RRuntime.STRING_NA}, ConstantType.STRING, src);
+        // don't intern NA value, otherwise each "NA" literal becomes an NA value
+        return new Constant(RRuntime.STRING_NA, src);
     }
 
     public void addNegativeSign() {
diff --git a/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/ast/FieldAccess.java b/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/ast/FieldAccess.java
index 18b51191d2..6dd48e261a 100644
--- a/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/ast/FieldAccess.java
+++ b/com.oracle.truffle.r.parser/src/com/oracle/truffle/r/parser/ast/FieldAccess.java
@@ -45,10 +45,7 @@ public final class FieldAccess extends ASTNode {
     public static ASTNode create(SourceSection src, FieldOperator op, ASTNode value, String fieldName) {
         switch (op) {
             case AT:
-                // these two names have special meaning for slot retrieval
-
-                String newName = fieldName.equals(RRuntime.DOT_DATA) ? RRuntime.DOT_DATA : (fieldName.equals(RRuntime.DOT_S3_CLASS) ? RRuntime.DOT_S3_CLASS : fieldName);
-                return new FieldAccess(src, value, newName, true);
+                return new FieldAccess(src, value, fieldName.intern(), true);
             case FIELD:
                 return new FieldAccess(src, value, fieldName, false);
         }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSerialize.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSerialize.java
index 0bba416246..ce0aaeb9c4 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSerialize.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/RSerialize.java
@@ -15,6 +15,7 @@ import java.io.*;
 import java.nio.charset.*;
 import java.util.*;
 
+import com.oracle.truffle.api.CompilerAsserts;
 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
 import com.oracle.truffle.api.frame.*;
 import com.oracle.truffle.api.source.*;
@@ -670,7 +671,7 @@ public class RSerialize {
 
                 case SYMSXP: {
                     String name = (String) readItem();
-                    result = RDataFactory.createSymbol(name);
+                    result = RDataFactory.createSymbol(name.intern());
                     addReadRef(result);
                     break;
                 }
@@ -1823,7 +1824,8 @@ public class RSerialize {
         RSymbol findSymbol(String name) {
             RSymbol symbol = symbolMap.get(name);
             if (symbol == null) {
-                symbol = RDataFactory.createSymbol(name);
+                CompilerAsserts.neverPartOfCompilation(); // for interning
+                symbol = RDataFactory.createSymbol(name.intern());
                 symbolMap.put(name, symbol);
             }
             return symbol;
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RDataFactory.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RDataFactory.java
index 6acc3e48d4..56016b5b14 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RDataFactory.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/data/RDataFactory.java
@@ -366,9 +366,17 @@ public final class RDataFactory {
     }
 
     public static RSymbol createSymbol(String name) {
+        assert name == name.intern();
         return traceDataCreated(new RSymbol(name));
     }
 
+    /*
+     * A version of {@link createSymbol} method used from native code.
+     */
+    public static RSymbol createSymbolInterned(String name) {
+        return createSymbol(name.intern());
+    }
+
     public static RLanguage createLanguage(RNode rep) {
         return traceDataCreated(new RLanguage(rep));
     }
diff --git a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/DLL.java b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/DLL.java
index 469e9f1c81..3fc09939fc 100644
--- a/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/DLL.java
+++ b/com.oracle.truffle.r.runtime/src/com/oracle/truffle/r/runtime/ffi/DLL.java
@@ -16,6 +16,7 @@ import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.*;
 
+import com.oracle.truffle.api.CompilerAsserts;
 import com.oracle.truffle.r.runtime.*;
 import com.oracle.truffle.r.runtime.RError.RErrorException;
 import com.oracle.truffle.r.runtime.context.*;
@@ -205,7 +206,8 @@ public class DLL {
     }
 
     public static RExternalPtr createExternalPtr(long value, RStringVector rClass) {
-        RExternalPtr result = RDataFactory.createExternalPtr(value, RDataFactory.createSymbol(rClass.getDataAt(0)));
+        CompilerAsserts.neverPartOfCompilation(); // for interning
+        RExternalPtr result = RDataFactory.createExternalPtr(value, RDataFactory.createSymbol(rClass.getDataAt(0).intern()));
         result.setClassAttr(rClass, false);
         return result;
     }
diff --git a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java
index 46e7b99886..72532269a0 100644
--- a/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java
+++ b/com.oracle.truffle.r.test/src/com/oracle/truffle/r/test/S4/TestS4.java
@@ -38,6 +38,8 @@ public class TestS4 extends TestBase {
         assertEval("{ getClass(\"ClassUnionRepresentation\")@virtual }");
         assertEval("{ getClass(\"ClassUnionRepresentation\")@.S3Class }");
         assertEval("{ c(42)@.Data }");
+        assertEval("{ x<-42; `@`(x, \".Data\") }");
+        assertEval("{ x<-42; `@`(x, .Data) }");
         assertEval(Output.ContainsError, "{ getClass(\"ClassUnionRepresentation\")@foo }");
         assertEval(Output.ContainsError, "{ c(42)@foo }");
         assertEval(Output.ContainsError, "{ x<-c(42); class(x)<-\"bar\"; x@foo }");
-- 
GitLab