Skip to main content

Const revisted

11 replies [Last post]
subanark
Offline
Joined: 2004-11-26

I have come up with a semantic for const values. Its a bit complex, but I think it will work. Its probably too late for const to be in mustang, but I hope it will be available in dolphin.

Definitions:

Readonly method
this is considered readonly. Readonly methods are non-external.
Getting a value from an array is considered constant.
Readonly methods may modify parameters if not declared constant.
(Constant methods are the same as readonly methods). Constructors cannot be readonly.

Non-External method
Method may only call non-external methods. May not reference static methods/fields unless either field or method is constant, or static method/field is declared in same class and method is static. (wait and notify should not be non-external).

Constant classes
All methods must be readonly or initializer. Initilizer methods must be non-external. Initilizers/constructors may not call non-initilizer methods. this is considered constant for all constant methods.

Constant reference/type
final, may only be assigned from another constant reference or from a non-external constructor invocation. May not invoke non-constant methods, all fields of object are considered constant. May not assign a constant reference to a non-constant or non-readonly reference unless the class of the reference is const.

Readonly reference
A reference which is constant except, you may not assign readonly references to constant references. Readonly references are not necessarily final. Normal references may be assigned to readonly references. The reverse is however not true. You cannot assign readonly references to normal references.

Initializer method
Must not be public. May only be called by constructors or other initilizer methods.
Static Initilizers may only be called by other static initializes or by the class constructor. Static Initilizers must be private. Constructors are considered Initilizer methods, except they may be called as they normally are.

Readonly classes
Just like constant classes, except that this is not considered constant.

Notes definitions:

Readonly methods:
Method guarantees that its state will not change when called. Const methods are an alias for readonly methods. Giving same parameters will result in same change of those parameters/return value.

Constant classes:
Class instances will never change after constructor is finished. Calling the same constructor with the same parameters results in an equivalent instance.

Non-external methods:
method will not change anything on the outside of itself or depend on any outside states.

Constant reference/type:
reference will never change.

Readonly reference:
All access to a readonly reference will not change the object, however the object can be changed externally (though a non-readonly reference to the object).

Readonly classes:
All methods of the class will not alter its state. Subclasses may however change the state of the class. Methods in readonly classes are not necessarily readonly. Invoking a constructor from a readonly class results in a const object.

Initilizer methods:
methods for initializing the method, and are only called during the initialization of the instance.

Rules:

Subclasses:
You may change readonly to const, or remove readonly. (this includes generic declarations on a class or a method).

Overriding methods:
you may override a method and provide a more restrictive modifier.
not restrictive: no modifier
more restrictive: readonly
most restrictive: const

Return values follow the same rules as parameters.

Object is a readonly class.

Since java uses erasure in generics, changing a generic declaration from readonly to const (of a type that is not const), will require that the caller wrap the passed parameter in a ConstantReference class which would merely have a single public const field for the const value (ConstantReference is a const class and thus can be assigned to non-const references). This is done by the compiler.

If you declare a generic parameter that extends a readonly parameter you cannot declare a non-final(or non-const/non-readonly) instance variable of that type. This is because if the type was const the runtime would not be able to enforce reassignment of that field. (Arrays are still ok)

Examples:

Classes like Vector can accept (and return) const references:
Assume all of Point’s constructor’s are declared non-external.
Assume Vector is declared: public class Vector
Then we can declare Vector as:
Vector points = new Vector();
We may now create a const Point:
const Point p = new Point(1,2);
we can add p to points:
points.add(p);
we can get a point back:
const Point p2 = points.get(0);
Assume that ConstantReference is:

<br />
public class ConstantReference<br />
{<br />
public T value;<br />
@NonExternal public ConstantReference(T  value)<br />
   {<br />
       this.value = value;<br />
   }<br />
}<br />

Internally however this is translated as:
<br />
points.add(new ConstantReference(p));<br />
const Point p2 = (const Point)((ConstantReference)points.get(0)).value;<br />

declaration examples:
<br />
public const Point foo(Point p); /*return type will be constant*/<br />
public Point const foo(Point p); /*method will not change its own state.*/<br />
public Point readonly foo(Point p); /*same as above*/<br />
public Point foo(readonly Point p); /*method will not change p.*/<br />
public Point foo(const Point p); /*p will never change, method may store p for itself.*/<br />

Assignment examples (assuming Point’s constructor is non-external):
<br />
Point p = new Point(1,1); /*legal*/<br />
const Point p2 = (const Point)p; /*not legal*/<br />
const Point p3 = new Point(1,1); /*legal*/<br />
readonly Point p4 = p; /*legal*/<br />
p4 = new Point(1,1); /*not legal, cannot change value of p4.*/<br />
readonly Point p5 = p3; /*legal*/<br />
p5.x = 5; /*not legal, cannot change p5*/<br />
p.x = 5; /*legal, p5’s x value is now 5.*/<br />
Object o = p3; /*not legal, o is not a const reference.*/<br />
/*(Assume String class is const)*/<br />
Object o2 = "Hello"; /*legal, String is a const class.*/<br />
String s = "Hello";<br />
const Object o3 = s; /*legal, String is const*/<br />
const Object o4 = (String)o3; /*legal, we have cast o3 to String which is a const class.*/<br />

Conclusion:
Hopefully if I did my calculations correctly, only the verifier needs to enforce these rules. Please tell me under these rules if there is any way to break the constness of the object (assuming jni/reflection behave). That is any way to change a field of the tree of an object, or cause a method to give different results if given the same parameters with the same object. Or if someone disagrees with my assumptions on what a const reference should be.

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
subanark
Offline
Joined: 2004-11-26

Well I've been working on a bytecode verifier for constant values for about a month now.
Lots of things to still do/bugs to fix, but here is my test cases I've created that the verifier catches:

[code]
/*
* Test1.java
*
* Created on March 5, 2005, 8:30 PM
*/

package javaconstants.test;
import javaconstants.*;
/**
*
* @author HarperArt
*/
public class Test1
{
static class Test2
{
public Object x;
@Unitilized Test2(){}
}
static int nonConstantStaticField = 0;
@Constant static int constantStaticField = 0;
@Constant static Object constObject;
static Object o;
Object lock;
static final int CONSTANT_VALUE = 0;
public static void objectTreeCheck(@NonExternal Test2 t)
{
Object newObject = new Test2(); /*Create a new Uninitilized object*/
t.x = newObject; /*newObject becomes initilized. The NON_EXTERNAL flag is set
since t is NON_EXTERNAL*/
o = newObject; /*Fail here. Our new object is non external and cannot leave.*/
}
public static void objectTreeCheck2(@NonExternal Test2 t)
{
Object newObject = new Test2(); /*Create a new Uninitilized object*/
o = newObject; /*newObject is now EXTERNAL since it was assigned to a static field.*/
t.x = newObject; /*Fail here. t is NON_EXTERNAL, we are trying to
assign an EXTERNAL value to one of its members.*/
}
public static void loopTest()
{
Test2 t = new Test2();
while(true)
{
unitilizedParameter(t); /*Fail here. Since we are in a loop, this instruction
can get executed after the next one.*/
nonConstParameters(t); /* Force t to become initilized.*/
}
}
public static void loopTest2()
{
Test2 test2 = new Test2();
nonConstParameters(test2);
int i = 0;
do
{
test2 = new Test2();
i++;
}
while(i < 3);
unitilizedParameter(test2); /*Ok, we know that test2 is assigned a new value*/

test2 = new Test2();
nonConstParameters(test2);
while(i < 3)
{
test2 = new Test2();
i++;
}
unitilizedParameter(test2); /*Fail here. The while loop might not run.*/
}
public static void forkConstValue()
{
if(nonConstantStaticField == 0)
nonConstantStaticField = 1;
else
constantStaticField = 2;/*Fail here. constantStaticField is declared to be constant*/
}
public void synchronizeTest()
{
int a = CONSTANT_VALUE;
synchronized(Test1.class)
{
o = null;
}
}
public static void ConstCast()
{
constantExternalParameter((String)o);/*We cast this object as a String,
so we may (from now on) treat it
as a constant or a non-constant
object.*/
}
public static void newObjectFromConstantClass()
{
String s = new String(); /*String is a const class*/
nonConstParameters(s); /*Since String is a const class, we know that
no changes will be made to it so we can pass
it around as if it were not constant.*/
constantExternalParameter(s); /*We may also pass instances of String to
constant expressions*/
}
public static void ifTest()
{
Test2 o = new Test2();
if(constantStaticField == 0)
{
nonConstParameters(o);
}
else
{
constantExternalParameter(o);
}
}
public static void newObjectFromConstantConstructor()
{
Test2 t = new Test2(); /*Creates a new Unitilized Object*/
constantExternalParameter(t); /*Calls a external method, which
forces the value to initilize. Also the object
is forced to be constant.*/
nonConstParameters(t); /*Fail here. Object is constant*/
}
public static void newObjectFromConstantConstructor2()
{
Test2 t = new Test2();
constantExternalParameter(t);
}
public static void newObjectFromConstantConstructor3()
{
Test2 t = new Test2();
nonConstParameters(t); /*Object is forced to be non-constant*/
}
public static void newObjectFromConstantConstructor4()
{
Test2 t = new Test2(); /*Creates a new Unitilized Object*/
nonExternalParameter(t); /*Since method is nonExternal, value is still unitilized*/
unitilizedParameter(t); /*This method requires that the value be unitilized*/
constantExternalParameter(t); /*Object is initilized, and made constant*/
}
@Constant
public Test1()
{

}

@NonExternal
public void accessStaticFromNonStatic()
{
int randValue = constantStaticField; /* this works, since constantStaticField is constant*/
randValue = nonConstantStaticField; /*Fail here. This method is delcared NonExternal, but it
uses data from outside its class that is not constant*/
}
@Constant
public static void accessStaticFromOtherClass()
{
int randValue = nonConstantStaticField; /*Works. nonConstantStaticField is from this class*/
java.io.PrintStream writer = System.out; /*Fail here. This method depends on a value
from outside its class*/
}
@Constant
public static void assignStatic()
{
nonConstantStaticField = 1; /*Fail here. Cannot assign class values from
a constant method.*/
}
public static void constantExternalParameter(@Constant Object a)
{
nonConstParameters(a); /*Fail here. Attempting to call a nonconst method,
with a const object.*/
}
public static void nonConstParameters(Object a)
{

}
public static void unitilizedParameter(@Unitilized Object a)
{

}
public static void nonExternalParameter(@NonExternal Object a)
{
}
public static void setConstValue()
{
constantStaticField = 1; /*Fail here. Cannot assign constant fields.*/
}
public static void main(String[] args)
{
System.out.println("Main started.");
}
}
[/code]

subanark
Offline
Joined: 2004-11-26

I have hit a minor problem (any help on figuring this out would be helpful):

Accessing static const values before they have been initilized.

E.g.
[code]
public class Test
{
static Point const p;
public static Point const notSoVerticlePoint;
static
{
notSoVerticlePoint = new VerticlePoint(new SpecialPoint());
p = notSoVerticlePoint.p;
}
}
public class SpecialPoint extends Point
{
@Override
public const int getX()
{
if(Test.p == null)
return 0;
else
return 1;
}
public const int getY()
{
return 2;
}
}
public class VerticlePoint
{
/*getX value should always be 0*/
public const Point p;
public VerticlePoint(const Point p)
{
if(p.getX() != 0)
throw new IllegalArgumentException("X has to be 0");
this.p = p;
}
}
public abstract class Point
{
public abstract Point const getX();
public abstract Point const getY();
}
[/code]
In this case the notSoVerticlePoint initially passes the test of having a 0 X value. But after Test.p is initilized, it now returns the value 1, thus violating the contract of const.

subanark
Offline
Joined: 2004-11-26

I think I now have the semantics down to 6 flags:
NON_EXTERNAL
EXTERNAL
CONSTANT
NON_CONSTANT
UNITILIZED
INITILIZED

Java objects without modifiers are:
EXTERNAL, NON_CONSTANT, INITILIZED, UNITILIZED

This means:
The object is allowed to be used in a put static.
The object may be modified (if methods allow it).
The object may or may not be initilized, and we cannot assume either one.

Objects declared as const are:
CONSTANT, INITILIZED

This means:
The object may be treated as external or non_external.
The object cannot be changed.
The object has been initlized.

Objects declared as readonly (and only readonly) are:
EXTERNAL,CONSTANT, NON_CONSTANT, INITILIZED, UNITILIZED
This means, we cannot assume the object is or is not constant.

Objects in classes declared as const (and not UNITILIZED) remove:
CONST,NON_CONST,EXTERNAL,NON_EXTERNAL

This means we may treat objects from const classes as either const or non const. (Since const classes enforce that all methods that are not initilizers cannot change the object). We may not however down cast an object to get this benifet, since we would not know if the object was still being initilized. If we were planning to be able to do this, we should declare our parameter as initilized.

If we wanted to allow the use of get static, and similar methods, then we would have to add another flag: FOREIGN, FOREIGN elements would not be allowed to interact with non-foriegn elements, but would allow const methods to use/invoke them. Any invocation involving a FOREIGN would have to be run on another thread, in order to prevent exceptions from happing, and from allowing foreign elements from accessing synchronized objects. In this case const methods could cause other things outside of the method to change, but could not use any of those changes. Threading could be avioded if there was a way to say no_access_if_synchronized(item,item2,...){...} where anything in ... (including method calls) would not be able to access the items (if not holding a lock on the item), and any attempt to do so would result in a deadlock exception.

Message was edited by: subanark

regexguy
Offline
Joined: 2003-06-20

I can see the benefit of having const objects -- memory savings, not having to code various bits of copy logic -- but I'm not sure who needs readonly references.

I kind of like having readonly as a modifier to a method (I think that's a better descriptive term than const when used on a method that won't change the object).

The question that's been raised before is whether a whole object should be marked as const (like the Pattern Enforcing Compiler does).

I would kind of like to have the ability to say
const Date = new const Date();
and
Date = new Date();

This would make it easier for me to migrate my existing code to use const values. I could make a subset of my existing usages const rather than the whole thing.

subanark
Offline
Joined: 2004-11-26

the readonly modifier is needed as a bridge between const and non-const.
When a method declares a parameter as readonly it in effect is stating that it will not modify that variable. This is the same contract that c++ uses.
This allows:
[code]
public class Foo
{
private int x;
private int y;
public void setPoint(readonly Point p)
{
this.x = p.x;
this.y = p.y;
}
}
[/code]

With the readonly modifier on p you can do:
[code]
Foo foo = new Foo();
const Point p = new Point(1,1);
foo.setPoint(p);
Point p2 = new Point(2,2);
foo.setPoint(p2);
[/code]

If you declared Point p to be const, then the caller could only pass const Points. If you declared Point p to not be const, then you could only pass non-const Points.

Readonly in this case means: I will not modify the passed parameter.
Const in this case means: This parameter will never be changed. Thus:
[code]
public class Foo()
{
private const Point p;
public void setPoint(const Point p)
{
this.p = p;
}
}
[/code]
This is fine since p will never change. We can just use its reference for our own purposes.

I probably should update my thinking in that:
const Point p; //p cannot be changed, but can be reassigned.
is not
Point const p; //p cannot be changed or reasigned.
final Point const p; //same as one above.

So it all depends on what const is modifing. We can take this to extremes:
Point[]const[][] p;
Which means you can reassign p and one of its dimensions. But you cannot change values in its other dimensions.
Essintally everything behind the const and one thing in front cannot be change.
So:
p = new Point[5][][]; is valid.
p[0] = new Point[3][]; is valid.
p[0][0] = new Point[2]; is not valid.

Next example:
initilize(const Point p)
{
p = new Point(); //p is considered unitilized for this scope.
p.x = 5;
p.y = 3;
}
constMethod(const p); //If we used c#'s syntax that is used in ref and out.
p.x = 7; //oops, we cannot modify p anymore.

initilize(Point p2)
{
p2 = new Point();
initilizationMethod(non_external p2);
p.x = 1;
someRandomMethod(p2); //oops someRandomMethod is not non_external. Cannot guraentee that it will store and change it latter.
someInitlizedMethod(initlizied p2); //oops p2 is not initilized. This method want to make sure that we are not initilizing it anymore.
}
someRandomMethod(p2); //now ok
constMethod(const p2); //oops. p2 not const.

Methods could also be declared as initilizers stating that they can only be called durring the initilization of the object. And methods may demand that it is no longer possible to call those methods before passing the values in. This would be usefull for classes which are partly constant.

regexguy
Offline
Joined: 2003-06-20

OK, I see that the readonly would be needed -- but it is not clear why the non-external and initializer attributes are needed.

If they really are, I think I'd rather see the PEC solution because this is looking too complex. We already swallowed the generic hairball (don't get me wrong, I love generics and think we needed them -- they just turned out to be more mindbending than I'd hoped).

subanark
Offline
Joined: 2004-11-26

non-external is needed for const methods.

The definition of const methods I'm going on is:
given the same state of the object, and the same state of the parameters passed to the const method, the results are the same, and the object will not be changed.

In order to enforce this contract, but give some flexibility to the method, the method will be allowed to call non_external methods. These methods are just like const methods, except they are allowed to modify "this".

If a const method was allowed to call a normal method, that normal method could have erratic behavior, thus the calling method would also have erratic behavior.

If a const method could only call other const methods, it may not be able to do its calculations. Also by my meaning of const methods, they are allowed to modify their passed parameters (unless declared as readonly).

Most methods should meet the requirements of being non_external. Unfortunately, simple things like System.out.println() would not be permitted, since out is a static field of System. (We could allow this by modifying our definition of non_external to allow calls to external data, so long as 1. return type if any is not used to modify any non_external references; 2. all exceptions are caught and ignored when making these calls).

initializer blocks are needed to prepare a value that will become const. They allow you to setup an object, when the constructor is not enough. Having uninitialize types floating around, is not needed for const, but adds the ability to separate methods that initialize an object, from those that don't care if they have an initialized object, to those that want an initialized object.

I am currently writing a javaagent to verify code declared with these annotations. Since if I can produce such a program, people will have the chance to try to break it, and will in general take me more seriously.

regexguy
Offline
Joined: 2003-06-20

I'm still confused... if a non-external method can modify "this", then how is it different from a normal method? Is this an attempt to do something like a C++ mutable?

subanark
Offline
Joined: 2004-11-26

A non_external method is guaranteed to result in the same operations (change in this and all parameters) for the same state of the object and all of its parameters.

If this is the case a const method may use a non_external non-const method (not on its self). It may for instance create an object, do some operations on it, and return it, while still guaranteeing that the method will result in the same result for the same state.

dog
Offline
Joined: 2003-08-22

Sure.. but what are the reasons that const is even necessary?

I think the merits need to be heavily explored. The benefits need to be very convincing before adding it to an already cluttered language.

I've used const in C++ and if often just got in the way because it doesn't seem to live well with code that doesn't obey its rules (existing Java code isn't currently compatible).

I for one would rather err on the side of simplicity and live without yet another C++ feature.

subanark
Offline
Joined: 2004-11-26

This would not be a c++ feature. Instead it would be a way of minimizing overhead of copying passed references, or relying on the caller to not change passed values.

For instance, you may derive Fonts using an AffineTransform, when you pass the affinetransform, it is copied and wraped into a TransformAttribute, to make it immuniable. If const was allowed, then this sort of behavior would no longer be necessary, you would be able to pass a const AffineTransform, no copy would need to be made. You could even subclass AffineTransform, and your subclass would be constant, it would behave as it should, except for methods like isIdentity.