8

Consider the following two classes. The first has simply a private Test__c and its constructor fills it in. The second class uses the first one and creates a Test__c object before instantiating the B class, just for demonstration.

public with sharing class A {
    private Test__c tst;

    public A (Id id) {
        this.tst = [select id, text__c from Test__c where id = :id];
    }

    public void saveTst () {
        update this.tst;
    }
}

public with sharing class B {
    public B () {
        Test__c tst = new Test__c();
        insert tst;

        A a = new A(tst.id);

        a.tst.text__c = 'test';
        //update a.tst; // if used, this line produces "Error: Compile Error: Variable is not visible: tst at line 9 column 16"
        a.saveTst();
    }
}

Now, what I would expect is that the sObject cannot be accessed from the outside (it's private) and that this would apply, of course, to its fields as well. However, although the first part is true and update a.tst will result in a compile error (as shown in the comment), the line before the commented one works! It is possible to set the private sObjects field and it will be committed to DB.

So, this obviously seems to be a huge bug. What I'm interested in is how did it happen. Are sObjects used in some specific way, possibly parsed out of the code together with fields in advance, so that the modifier gets ignored?

The extra annoying part is that even the update a.tst part will work if I put the classes in an anonymous block (tried in dev console). I know classes in anonymous are treated as inner classes, but why would that matter here? That tst is still private to the A class!

Anyone less confused than me?

EDIT: After quite a bit of time wrestling with SF support, the issue was finally propagated to R&D and recognized as a bug: https://success.salesforce.com/issues_view?id=a1p30000000T22eAAC

1 Answer 1

5

I replicated this in my developer environment. This is definitely a bug, and I would submit a case to Technical Support so they can get this over to a PM. The issue lies in the scope resolution process in the compiler. Note that if you replace tst data type with a public class, then the expected behavior occurs-- tst would be inaccessible even if you tried to set a member of tst.

This quote may help you understand the order of operations:

Type Resolution and System Namespace for Types

Because the type system must resolve user-defined types defined locally or in other classes, the Apex parser evaluates types as follows:

For a type reference TypeN, the parser first looks up that type as a scalar type.

If TypeN is not found, the parser looks up locally defined types.

If TypeN still is not found, the parser looks up a class of that name.

If TypeN still is not found, the parser looks up system types such as sObjects.

For the type T1.T2 this could mean an inner type T2 in a top-level class T1, or it could mean a top-level class T2 in the namespace T1 (in that order of precedence).

It appears that the "SObject" part of the chain is where it's broken-- once it has determined that an SObject is involved, it appears that the system loses the ability to check to see if that SObject variable has access or not.

1
  • True; when I tried similar with an object instead of sobject, it worked as expected (although I didn't test it much). Well, I raised a case. What I'm wondering is how many people have gone through the same process as myself: code something directly, switch to a wrapper object, forget to change lines that access these fields directly. Now, if this bug gets solved, those lines could pop up. Thank you for the explanation. I thought it could be some shortcut in parsing.
    – zdropic
    Commented Nov 11, 2013 at 16:42

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .