2

Context :

  • package data, public :
public interface _Data {
   public String getData();
}
public class _PackageAPI {
    DataHolder holder;

    public void createHolder(String data) {
        holder = new DataHolder();
        holder.setData(data);
    }

    public void mutateHolder(String data) {
        holder.setData(data);
    }

    public _Data getSnapshot() {
        return DataSnapshot.from(holder.getData());
    }

    public _Data getReader() {
        return holder.readOnly();
    }
}
  • package data, package-private :
class DataHolder {
    private String data;

    public String getData() {
        return data;
    }

    public void setData(String data) {
        this.data = data;
    }

    public _Data readOnly() {
        return new _Data() {
            @Override
            public String getData() {
                return DataHolder.this.data;
            }
        };
    }
}
class DataSnapshot {
    public static _Data from(String data){
        return new _Data() {
            @Override
            public String getData() {
                return data;
            }
        };
    }
}
  • sample client usage :
package clientPackage;

import data._Data;
import data._PackageAPI;

public class ExampleRunner {

    public static void main(String[] args) {

        _PackageAPI handler;

        System.out.println("Creating...");

        handler = new _PackageAPI();
        handler.createHolder("INITIAL DATA");

        System.out.println("Done creating...");

        _Data initialSnapShot =  handler.getSnapshot();
        _Data initialReader = handler.getReader();

        System.out.println("initialSnapShot holds :" + initialSnapShot.getData() );
        System.out.println("initialSnapShot class :" + initialSnapShot.getClass() );
        System.out.println("initialReader class :" + initialReader.getClass() );

        System.out.println("initialReader holds :" + initialReader.getData() );


        System.out.println("Mutating...");

        handler.mutateHolder("MUTATED DATA");
        _Data subsequentSnapShot =  handler.getSnapshot();
        _Data subsequentReader = handler.getReader();

        System.out.println("Done mutating...");


        System.out.println("initialSnapShot holds :" + initialSnapShot.getData() );
        System.out.println("initialReader holds :" + initialReader.getData() );

        System.out.println("subsequentSnapShot holds :" + subsequentSnapShot.getData() );
        System.out.println("subsequentReader holds :" + subsequentReader.getData() );



    }
}
  • And console output:
Creating...
Done creating...
initialSnapShot holds :INITIAL DATA
initialSnapShot class :class data.DataSnapshot$1
initialReader class :class data.DataHolder$1
initialReader holds :INITIAL DATA
Mutating...
Done mutating...
initialSnapShot holds :INITIAL DATA
initialReader holds :MUTATED DATA
subsequentSnapShot holds :MUTATED DATA
subsequentReader holds :MUTATED DATA
  • FIRST QUESTION : given getSnapshot() returns a _Data (of class : DataSnapshot$1) whose method getData() returns the "real" data reference, ie the content of the variable data of the DataHolder object, is this safe or is it somehow possible to mutate DataHolder leveraging access to this reference? If yes, how ?

  • FIRST QUESTION SHORTENED : is it anyhow possible to mutate content of the memory referenced by a reference, using only the reference ?

(Of course solution to this is to clone the String being referenced.)

  • SECOND QUESTION : is there anyway to mutate a DataSnapshot$1 (the "immutable" version of _Data) instance ?

  • THIRD QUESTION : given DataHolder$1 (the "readOnly" version of _Data) holds internally a reference to the DataHolder instance providing it, is it safe to expose such a DataHolder$1, or is there anyway to mess with the DataHolder instance from the DataHolder$1 object ?

EDIT : I would have put a paranoïd tag if there was one

ceng
  • 128
  • 3
  • 17
  • Regarding your first question, do you realise you are getting a reference to a `String` in `getSnapshot().getData()`, and `String` is immutable? – Sweeper Apr 06 '20 at 18:55
  • @Sweeper Yes, i realize that. And if String was mutable, i would have to clone it anywhere before letting it out of dataholder of course –  Apr 06 '20 at 19:01
  • for an immutable, first make all instance variable as private, second remove all setters, third make final either class or all getters so no one can override them, and use constructor to set the instance variables. –  Apr 06 '20 at 19:06
  • @AhmetOZKESEK the problem is i use hibernate so i need to have a noArgs constructor, can't make nor my class nor fields final and so on, this is why having a proper immutable class is not an option. Plus ids need to be set by the database so the class can not be immutable –  Apr 06 '20 at 19:13
  • What exactly is it you are afraid of? Are you running untrusted code? Because remember that if the java code can use Reflection, not even strings are immutable – MTilsted Apr 06 '20 at 19:28
  • @MTilsted Isn't one of the main purposes of immutability to prevent human error? Sometimes people accidentally change something, but in another part of the code, they don't realise they changed it. – Sweeper Apr 06 '20 at 19:31
  • @Sweeper Yes that is the normal purpose, but since the author used the phrase "I would have put a paranoïd tag if there was one" This might in fact be a question about security, not just human error. – MTilsted Apr 06 '20 at 19:37
  • @MTilsted That sentence was edited in by someone else, not the OP though... – Sweeper Apr 06 '20 at 19:38

1 Answers1

0

is this safe or is it somehow possible to mutate DataHolder leveraging access to this reference? If yes, how ?

Since getData returns a String, which is immutable, the caller can't change anything through the returned reference. You can't access a DataHolder through a String either. Why would String know about your DataHolder class?

is there anyway to mutate a DataSnapshot$1?

No, because it is an anonymous inner class that only has one method that returns a parameter. Parameters are passed by value, so you don't need to worry about callers changing their values on the other side. It's also a String, which means the callers won't be mutating the object either.

You might be asking this because you saw how initialReader has changed. Well, since DataHolder$1 is an inner class of the mutable DataHolder, it's not really immutable even if it doesn't have any mutator methods.

is it safe to expose such a DataHolder$1, or is there anyway to mess with the DataHolder instance from the DataHolder$1 object ?

There is no way to access the outer class from the inner class, so since there are no mutator methods in DataHolder$1, you can't mutate DataHolder from the outside, with only a DataHolder$1.

However, if DataHolder changes, the changes will reflect on DataHolder$1 (as shown in your sample code), which I think defeats the purpose of immutability.


Here's how I would implement immutability in this scenario.

I would make DataHolder implement _Data. DataHolder certainly can do this, can't it? It has a String getData() method after all! DataHolder would have an asReadOnly method that creates a copy of this and returns _Data. In fact, I'd rename _Data to ReadOnlyData.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • First, thanks for the clear, detailed and documented answer ! Two remarks : 1. "which I think defeats the purpose of immutability" - can you expand on this ? 2. i saw about your suggested implementation in some articles and on stackoverflow, this is often criticized by the fact that all you need to do is cast back to the mutable class (and package-private classes are not always feasible in practice) –  Apr 06 '20 at 19:41
  • @nasd4q 1. one main purpose of immutability is to be able to pass objects around knowing for sure that they will stay the same, isn't it? You wouldn't expect to get different results when you do `getData()` on the same `_Data` object twice, right? But if `DataHolder` were mutated between the two calls, the return values will indeed be different. 2. I interpreted your question as that you want to prevent human error, so although it is possible, you don't accidentally cast to the original class, do you? – Sweeper Apr 06 '20 at 19:45
  • @nasd4q Of course, it depends on what you intends to achieve with immutability. Your `DataSnapshot` class here is a good approach as well. – Sweeper Apr 06 '20 at 19:47
  • your answer is quite good and sufficient for all intents and purposes (which was originally only to have a way to prevent mess-generating human errors). One topic not yet covered though is what kind of mess would be imaginable using reflection and alikes, just out of curiosity. Anyway thanks –  Apr 06 '20 at 20:33
  • @nasd4q See https://stackoverflow.com/questions/29246028/how-to-prevent-access-via-reflection for reflection related stuff. – Sweeper Apr 06 '20 at 20:36