Discussion:
Assignment to record variable parameter
(too old to reply)
August Karlstrom
2017-01-30 13:34:59 UTC
Permalink
Assuming that T is a record type, is this valid Oberon code?

PROCEDURE P(VAR x: T);
VAR y: T;
BEGIN
x := y
END P

If yes, should the program trap if the actual parameter x has a type
which is a proper extension of T?


-- August
c***@gmail.com
2017-02-04 00:48:49 UTC
Permalink
Post by August Karlstrom
Assuming that T is a record type, is this valid Oberon code?
PROCEDURE P(VAR x: T);
VAR y: T;
BEGIN
x := y
END P
If yes, should the program trap if the actual parameter x has a type
which is a proper extension of T?
That example code is syntactically correct and will compile and probably run without trapping. However, it is a hypothetical scenario that has no practical use. Too many details are missing to give a conclusive answer to your question. e.g. What are the initial values of y? Can T be a POINTER type? etc. etc.

A practical real-world example that was designed to be used with extended types would include type tests and type guards would be used to trap errors. Do you have a similar question related to such an example?

Regards,
Chris Burrows
CFB Software
http://www.astrobe.com
August Karlstrom
2017-02-04 11:38:21 UTC
Permalink
Post by c***@gmail.com
Post by August Karlstrom
Assuming that T is a record type, is this valid Oberon code?
PROCEDURE P(VAR x: T);
VAR y: T;
BEGIN
x := y
END P
If yes, should the program trap if the actual parameter x has a type
which is a proper extension of T?
That example code is syntactically correct and will compile and
probably run without trapping. However, it is a hypothetical scenario
that has no practical use.
Probably so, but that is often the case with loopholes in programming
languages.
Post by c***@gmail.com
Too many details are missing to give a conclusive answer to your
question. e.g. What are the initial values of y?
In my example the value of y is irrelevant. The general rule says that a
variable of an extended type can be assigned to a variable of a base
type. In my example the reverse happens if the actual type of x is an
extension of T.
Post by c***@gmail.com
Can T be a POINTER type? etc. etc.
If T is a pointer it's even worse; the dynamic type of x may change to a
type which is not an extension of the declared type:

T = POINTER TO RECORD END

x: POINTER TO RECORD (T) END

P(x)
(*the dynamic type of x is now T*)
Post by c***@gmail.com
A practical real-world example that was designed to be used with
extended types would include type tests and type guards would be
used to trap errors.
Good point! A compiler could also reject P as declared above. After all,
it cannot guarantee that the actual type of x really is T. A valid
version of P would then look something like this:

PROCEDURE P(VAR x: T);
VAR y: T;
BEGIN
IF x IS T THEN
x(T) := y
END
END P
Post by c***@gmail.com
Do you have a similar question related to such an example?
No, the above looks alright to me. What do you think?


Regards,

August
c***@gmail.com
2017-02-06 11:37:03 UTC
Permalink
Post by August Karlstrom
Good point! A compiler could also reject P as declared above. After all,
it cannot guarantee that the actual type of x really is T. A valid
PROCEDURE P(VAR x: T);
VAR y: T;
BEGIN
IF x IS T THEN
x(T) := y
END
END P
Post by c***@gmail.com
Do you have a similar question related to such an example?
No, the above looks alright to me. What do you think?
No - it still has no use so is irrelevant. As soon as you initialise values for y to make it do something useful you will find that you are constrained in what you can do. For example, given the following declarations:

TYPE
T0 = RECORD i: INTEGER END;
T1 = RECORD (T0) r: REAL END;

VAR
t0: T0;
t1: T1;

If I wanted to use your style of example to zero out the values for both types of records then it might look something like this:

PROCEDURE Init(VAR x: T0);
VAR
y: T1;
BEGIN
y.i := 0;
y.r := 0.0;
x := y
END Init;

Can you subvert that without getting compilation or runtime errors?

The following alternative would give a runtime trap when passed t0 as a parameter:

PROCEDURE Init(VAR x: T0);
BEGIN
x.i := 0;
x(T1).r := 0.0
END Init;

This would work:

PROCEDURE Init(VAR x: T0);
BEGIN
x.i := 0;
IF x IS T1 THEN x(T1).r := 0.0 END
END Init;

Regards,
Chris Burrows
CFB Software
http://www.astrobe.com
August Karlstrom
2017-02-06 17:41:53 UTC
Permalink
Post by c***@gmail.com
Post by August Karlstrom
PROCEDURE P(VAR x: T);
VAR y: T;
BEGIN
IF x IS T THEN
x(T) := y
END
END P
Post by c***@gmail.com
Do you have a similar question related to such an example?
No, the above looks alright to me. What do you think?
No - it still has no use so is irrelevant.
My example above is purely theoretical. There is no actual problem I'm
trying to solve with this code. My point is to illustrate that a
compiler must either reject or trap at run-time an attempt to assign a
variable of type T to a variable of a type which (properly) extends T.
Compile-time rejection is of course preferable and that's why I think a
compiler should reject the following declaration:

PROCEDURE P(VAR x: T);
VAR y: T;
BEGIN
x := y
END P
Post by c***@gmail.com
As soon as you initialise values for y to make it do something useful
you will find that you are constrained in what you can do. For
example, given the following declarations: >
TYPE
T0 = RECORD i: INTEGER END;
T1 = RECORD (T0) r: REAL END;
VAR
t0: T0;
t1: T1;
If I wanted to use your style of example to zero out the values for
PROCEDURE Init(VAR x: T0);
VAR
y: T1;
BEGIN
y.i := 0;
y.r := 0.0;
x := y
END Init;
The assignment x := y should be rejected by the compiler.
Post by c***@gmail.com
Can you subvert that without getting compilation or runtime errors?
Yes, if I'm not mistaken you typically have one initialization procedure
for each type:

PROCEDURE InitT0(VAR x: T0);
BEGIN
x.i := 0
END InitT0

and

PROCEDURE InitT1(VAR x: T1);
BEGIN
InitT0(x);
x.r := 0.0
END InitT1


Regards,
August
c***@gmail.com
2017-02-07 03:03:56 UTC
Permalink
Post by August Karlstrom
Post by c***@gmail.com
PROCEDURE Init(VAR x: T0);
VAR
y: T1;
BEGIN
y.i := 0;
y.r := 0.0;
x := y
END Init;
The assignment x := y should be rejected by the compiler.
That is not correct. y is of type T1 which is an extension of type T0 which is the type of x so the assignment is valid. Refer to Section 9.1 "Assignments" of the 2016 revison of the Oberon language report.

On the other hand, if the source and destination are switched i.e. y := x that would be rejected by the compiler as an illegal assignment. Maybe that is what you were thinking of?

Regards,
Chris Burrows

CFB Software
http://www.astrobe.com
August Karlstrom
2017-02-07 10:31:38 UTC
Permalink
Post by c***@gmail.com
Post by August Karlstrom
Post by c***@gmail.com
PROCEDURE Init(VAR x: T0);
VAR
y: T1;
BEGIN
y.i := 0;
y.r := 0.0;
x := y
END Init;
The assignment x := y should be rejected by the compiler.
That is not correct. y is of type T1 which is an extension of type T0
which is the type of x so the assignment is valid. Refer to Section
9.1 "Assignments" of the 2016 revison of the Oberon language report.
The formal type of x is indeed T0 but the type of the actual parameter x
is a (possibly proper) extension of T0. As far as I understand, section
9.1 applies to the actual type in this case, not the formal. Do you see
any problems with the compiler rejecting the above example and instead
force the use of type tests/guards?

-- August
August Karlstrom
2017-02-07 11:12:23 UTC
Permalink
Post by August Karlstrom
Assuming that T is a record type, is this valid Oberon code?
PROCEDURE P(VAR x: T);
VAR y: T;
BEGIN
x := y
END P
If yes, should the program trap if the actual parameter x has a type
which is a proper extension of T?
Here is a complete example which illustrates the problem with pointer
variable parameter assignments:

MODULE PointerVarParamAssignnment;

TYPE
P0 = POINTER TO RECORD END;
P1 = POINTER TO RECORD (P0) END;

PROCEDURE P(VAR x: P0);
VAR y: P0;
BEGIN
NEW(y);
x := y (*cannot allow actual parameter x: P1 to become y: P0*)
END P;


PROCEDURE Test;
VAR x: P1;
BEGIN
P(x)
END Test;

BEGIN
Test
END PointerVarParamAssignnment.


-- August
c***@gmail.com
2017-02-07 12:13:24 UTC
Permalink
Post by August Karlstrom
Here is a complete example which illustrates the problem with pointer
MODULE PointerVarParamAssignnment;
TYPE
P0 = POINTER TO RECORD END;
P1 = POINTER TO RECORD (P0) END;
PROCEDURE P(VAR x: P0);
VAR y: P0;
BEGIN
NEW(y);
x := y (*cannot allow actual parameter x: P1 to become y: P0*)
END P;
PROCEDURE Test;
VAR x: P1;
BEGIN
P(x)
END Test;
BEGIN
Test
END PointerVarParamAssignnment.
I recommend you re-read Section 19. "Type Extension, Type Tests and –Guards" in "An Oberon Compiler for the ARM Processor."

https://www.inf.ethz.ch/personal/wirth/Oberon

It gives the best explanation I have seen of how all of this is intended to work. Does this correspond with your current understanding?

Chris.
August Karlstrom
2017-02-07 15:55:41 UTC
Permalink
Post by c***@gmail.com
Post by August Karlstrom
Here is a complete example which illustrates the problem with pointer
MODULE PointerVarParamAssignnment;
TYPE
P0 = POINTER TO RECORD END;
P1 = POINTER TO RECORD (P0) END;
PROCEDURE P(VAR x: P0);
VAR y: P0;
BEGIN
NEW(y);
x := y (*cannot allow actual parameter x: P1 to become y: P0*)
END P;
PROCEDURE Test;
VAR x: P1;
BEGIN
P(x)
END Test;
BEGIN
Test
END PointerVarParamAssignnment.
I recommend you re-read Section 19. "Type Extension, Type Tests and
–Guards" in "An Oberon Compiler for the ARM Processor."
https://www.inf.ethz.ch/personal/wirth/Oberon
Thanks for the link. I have read parts of it before.
Post by c***@gmail.com
It gives the best explanation I have seen of how all of this is
intended to work. Does this correspond with your current understanding?
Yes, it corresponds with my current understanding. However, it doesn't
say if the assignment in the program above should be prevented at
compile-time or at run-time. What would be the drawback of preventing it
at compile-time?

To me, it's not clear why the runtime needs to keep track of all base
types of a given type. To my mind, a single (hidden) type ID field in
every record would suffice. If there was an "is extension of" operator
that would make sense.

-- August
August Karlstrom
2017-02-14 11:15:13 UTC
Permalink
Post by August Karlstrom
To me, it's not clear why the runtime needs to keep track of all base
types of a given type. To my mind, a single (hidden) type ID field in
every record would suffice. If there was an "is extension of" operator
that would make sense.
I was wrong here. Type guards, type tests and type cases with type T are
valid if the dynamic type is T *or an extension thereof*.

-- August
s***@gmail.com
2017-02-25 09:29:58 UTC
Permalink
In Oberon in the case of variable parameters both actual and formal parameters must have the same type, except in the case of extended records.

Passing a extended pointer to a variable parameter is a compile-time error.

Ulm Oberon is the only Oberon compiler that made an exception and it's documented in Language differences.



MODULE LoopholeVarPointer;

TYPE
Ta = RECORD a : INTEGER END;
Tb = RECORD (Ta) b : INTEGER END;

Pa = POINTER TO Ta;
Pb = POINTER TO Tb;

VAR
pb : Pb;

PROCEDURE F(VAR pa : Pa);
BEGIN
NEW(pa); (* allocated as Ta *)
END F;

BEGIN
F(pb); (* pb will be allocated as Ta *)
pb.b := 1; (* ??? *)
END LoopholeVarPointer.
August Karlstrom
2017-02-25 10:40:07 UTC
Permalink
Post by s***@gmail.com
In Oberon in the case of variable parameters both actual and formal
parameters must have the same type, except in the case of extended
records.
Passing a extended pointer to a variable parameter is a compile-time error.
That's interesting.
Post by s***@gmail.com
Ulm Oberon is the only Oberon compiler that made an exception and
it's documented in Language differences.
MODULE LoopholeVarPointer;
TYPE
Ta = RECORD a : INTEGER END;
Tb = RECORD (Ta) b : INTEGER END;
Pa = POINTER TO Ta;
Pb = POINTER TO Tb;
VAR
pb : Pb;
PROCEDURE F(VAR pa : Pa);
BEGIN
NEW(pa); (* allocated as Ta *)
END F;
BEGIN
F(pb); (* pb will be allocated as Ta *)
pb.b := 1; (* ??? *)
END LoopholeVarPointer.
If the type descriptor of Pb is passed along with the pointer pa (as a
hidden parameter) the call to NEW can allocate a variable of type Tb
instead. Maybe that's how it's done in Ulm Oberon.


-- August
Diego Sardina
2017-02-25 20:12:51 UTC
Permalink
Post by August Karlstrom
If the type descriptor of Pb is passed along with the pointer pa (as a
hidden parameter) the call to NEW can allocate a variable of type Tb
instead. Maybe that's how it's done in Ulm Oberon.
You arouse my curiosity :-)
I played with Ulm Oberon long time ago and the above code did not crash or gave unexpected results (typical of loopholes). I haven't investigated further at that time, but I supposed pb.b overwrites an adiacent block in the heap.

But looking at the first field of the type descriptor (in Ulm Oberon it's the type's size):
SYSTEM.GET(SYSTEM.ADR(pb^)-4, adr);
SYSTEM.GET(adr, tpsize);
Write.Int(tpsize, 1);

It outputs 8 (instead of 4), then you are right and it was allocated as Tb and not Ta! A nice trick to avoid the loophole (although not very intuitive).

Now the real question: is it a safe trick? Well, we are allocating a variable of type Ta as Tb, but Tb *is* Ta. The world is safe.

But there is another question: why this trick has been excluded in Oberon? There are two reasons that come to my mind:

1) Code obscurity: we are allocating a variable of a different type (although an extension of it) with a different size. This defeats predictability of code.

2) Another hidden parameter: in Oberon only two types of parameters have a hidden parameter, open arrays and variable parameters of records. This requires that also variable parameters of pointers must carry a hidden parameter.

Wirth is usually against hidden things, but I'm more inclined to believe that 1) is the main reason.
August Karlstrom
2017-02-26 11:27:36 UTC
Permalink
Post by Diego Sardina
But there is another question: why this trick has been excluded in
1) Code obscurity: we are allocating a variable of a different type
(although an extension of it) with a different size. This defeats
predictability of code.
I don't think it's obscure. "A variable parameter corresponds to an
actual parameter that is a variable, and it stands for that variable."
In your example I would expect NEW to allocate a variable of type Tb.
Post by Diego Sardina
2) Another hidden parameter: in Oberon only two types of parameters
have a hidden parameter, open arrays and variable parameters of
records. This requires that also variable parameters of pointers must
carry a hidden parameter.
Wirth is usually against hidden things, but I'm more inclined to
believe that 1) is the main reason.
I think it has to do with its limited use. Why complicate the
implementation to solve a corner case? Can we think of a situation where
this type compatibility between a variable pointer parameter and its
extensions is essential?


-- August
c***@gmail.com
2017-02-14 02:37:37 UTC
Permalink
Post by August Karlstrom
Assuming that T is a record type, is this valid Oberon code?
PROCEDURE P(VAR x: T);
VAR y: T;
BEGIN
x := y
END P
If yes, should the program trap if the actual parameter x has a type
which is a proper extension of T?
I have investigated further since my previous replies and have found a good explanation of these issues in Hanspeter Mossenbock's book: Object-Oriented Programming in Oberon-2. You can download a PDF copy from http://ssw.jku.at/Research/Books/

Chapter 5 is most relevant to your question. An identical example is explored in Section 5.3 "Static and Dynamic Type" as follows:

"Oberon-2 solves this problem by permitting the assignment to a variable parameter record only if its dynamic type is the same as its static type."

There is no error at compile-time. However, if the actual parameter x has a type which is a proper extension of T the assignment x := y is not permitted. This check is done at runtime.

So, yes, according to that, the program should trap at runtime whenever that is the case.

Regards,
Chris Burrows
CFB Software
http://www.astrobe.com
August Karlstrom
2017-02-14 10:13:25 UTC
Permalink
Post by c***@gmail.com
Post by August Karlstrom
Assuming that T is a record type, is this valid Oberon code?
PROCEDURE P(VAR x: T);
VAR y: T;
BEGIN
x := y
END P
If yes, should the program trap if the actual parameter x has a type
which is a proper extension of T?
I have investigated further since my previous replies and have found
Object-Oriented Programming in Oberon-2. You can download a PDF copy
from http://ssw.jku.at/Research/Books/
I actually have the book; it's excellent!
Post by c***@gmail.com
Chapter 5 is most relevant to your question. An identical example is
"Oberon-2 solves this problem by permitting the assignment to a
variable parameter record only if its dynamic type is the same as
its static type."
There is no error at compile-time. However, if the actual parameter
x has a type which is a proper extension of T the assignment x := y
is not permitted. This check is done at runtime.
So, yes, according to that, the program should trap at runtime
whenever that is the case.
Thanks for looking into this. I must have read it but I haven't opened
the book in a long time.


-- August
c***@gmail.com
2017-03-13 11:31:31 UTC
Permalink
Post by c***@gmail.com
"Oberon-2 solves this problem by permitting the assignment to a variable parameter record only if its dynamic type is the same as its static type."
There is no error at compile-time. However, if the actual parameter x has a type which is a proper extension of T the assignment x := y is not permitted. This check is done at runtime.
So, yes, according to that, the program should trap at runtime whenever that is the case.
On further investigation I have discovered that I was wrong. There should be a compile-time error as Diego Sardina later pointed out. However, the error does not occur in the body of the procedure, but in the call to the procedure. This check will be implemented in the next Astrobe ARM Cortex-M compiler releases.

--
Chris Burrows
CFB Software
http://www.astrobe.com
c***@gmail.com
2018-12-12 07:06:45 UTC
Permalink
Post by August Karlstrom
Assuming that T is a record type, is this valid Oberon code?
PROCEDURE P(VAR x: T);
VAR y: T;
BEGIN
x := y
END P
If yes, should the program trap if the actual parameter x has a type
which is a proper extension of T?
-- August
Hi August,
Just for information - in my Oberon compiler, I know from experience that the example code will compile fine, but tends to give a type error at runtime when the assign occurs, especially with complex record variables. I am not sure why this is, I haven't yet had the time or energy to debug the generated machine code to check why the type tags don't match (it is not entirely trivial to debug with record variables, it becomes quite a complex exercise).
August Karlstrom
2018-12-12 17:49:06 UTC
Permalink
Post by c***@gmail.com
Post by August Karlstrom
PROCEDURE P(VAR x: T);
VAR y: T;
BEGIN
x := y
END P
Just for information - in my Oberon compiler, I know from experience that the example code will compile fine, but tends to give a type error at runtime when the assign occurs, especially with complex record variables. I am not sure why this is, I haven't yet had the time or energy to debug the generated machine code to check why the type tags don't match (it is not entirely trivial to debug with record variables, it becomes quite a complex exercise).
According to my (current) understanding, the example should compile but
trap at run-time if x is a proper extension of T.


-- August

Loading...