- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Compiler: ifx (IFX) 2025.0.4
OS: Rocky Linux 8.9 (Green Obsidian)
I’ve got another strange one. The code compiles but gives a bogus memory address when printed. In short, one of the pointers inexplicably reassigns to a different patch of memory.
The following is a much-reduced example from our team’s architecture; it is longer than I would like, but I could not find a more streamlined way to demonstrate the bug. The memory address printed at lines 132, 133, 141, and 142 should all be the same, but the last one is different. While the current example does not produce a runtime error, it did err at different stages as I slowly paired down the original case into this example.
module Actor_Header
implicit none
type :: Actor
integer :: ii
end type
type ActorContainerElement
class(Actor), pointer :: object => null()
type(ActorContainerElement), pointer :: ptr => null() ! comment out this line and everything is fine
end type
type ActorContainer
type(ActorContainerElement), pointer, public :: item => null()
contains
final :: finalize
generic, public :: assignment(=) => copyContainer
procedure, public :: add
procedure, private :: copyContainer
end type
contains
subroutine finalize(this)
type(ActorContainer) :: this
deallocate(this%item)
end subroutine
subroutine copyContainer(lhs,rhs)
class(ActorContainer), intent(out) :: lhs
class(ActorContainer), intent(in) :: rhs
call lhs%add(rhs%item%object)
end subroutine
subroutine add(this,something)
class(ActorContainer), target, intent(inout) :: this
class(Actor), target, intent(in) :: something
allocate(this%item)
this%item%object => something
end subroutine
end module
module Class1_Header
implicit none
type :: Class1
logical :: bool = .false.
end type
type Class1ContainerElement
class(Class1), allocatable :: object
type(Class1ContainerElement), pointer :: ptr => null() ! comment out this line and everything is fine
end type
type Class1Container
type(Class1ContainerElement), pointer :: item => null()
contains
procedure, public :: add
end type
contains
subroutine add(this,something)
class(Class1Container), intent(inout) :: this
class(Class1), intent(in) :: something
allocate(this%item)
this%item%object = something
end subroutine
end module
module Class2_Header
use Actor_Header
implicit none
type :: Filter1
type(ActorContainer) :: actors
end type
type :: CompositeFilter
type(Filter1) :: subFilter
end type
type :: Class2
type(CompositeFilter), allocatable :: filter
end type
contains
function NewClass2(anActor) result(this)
class(Actor), intent(in) :: anActor
type(Class2) :: this
allocate(this%filter)
call this%filter%subFilter%actors%add(anActor)
end function
end module
program main
use Actor_Header
use Class1_Header
use Class2_Header
implicit none
type(Actor) :: anActor
type(Class2) :: myClass2
class(Actor), pointer :: pActor => null()
type(Class1) :: myClass1
type(class1Container) :: myClass1Container
print *, 'the following 4 memory addresses should be the same'
! create nested class structure
anActor%ii = 41
myClass2 = NewClass2(anActor)
! compare address of original object to underlying pointer
pActor => myClass2%filter%subFilter%actors%item%object
print *, ' actor address', loc(anActor)
print *, 'pointer address', loc(pActor)
! add a totally unrelated object to a totally unrelated container
! comment out this line and the memory address issue is fixed...
call myClass1Container%add(myClass1)
! compare address of original object to underlying pointer
pActor => myClass2%filter%subFilter%actors%item%object
print *, ' actor address', loc(anActor)
print *, 'pointer address', loc(pActor)
! check that original object still exists (it does)
anActor%ii = anActor%ii + 1
print *, 'original object', anActor%ii, ' (should be 42 if original object still exists)'
end program
This code is also attached in a file below. Any and all help is appreciated.
Additional Details
There are several similarities between this bug and another issue I submitted but also some key difference. The main contrast is that the previous bug dealt with the wrong procedure being called, while this one shows a pointer is being unintentionally altered.
Note some of the oddities I observed when pairing down our architecture to the given example:
- Commenting out line 137 causes the error to vanish, yet it has nothing to do with the rest of the example.
- Commenting out line 11 or 58 (or both) also fixes the problem.
These are not realistic workarounds for our situation; again, this is a significant simplification from our framework. Certain aspects seem silly separated from their context. I am simply explaining why certain features were included in the bugged example.
Link Copied
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Note that this is stilled bugged in 2025.1.0
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
I found the underlying issue here. In short, the defined assignment operator is not called within the nested class structure. This is why the memory address was sometimes wrong in the above code. Here is a simplified example that shows that the defined assignment is not called, which is a bug:
module Classes_Header
implicit none
type :: class1
integer :: ii
contains
generic, public :: assignment(=) => copy
procedure, private :: copy
end type
type :: class2
type(class1) :: myClass1
end type
type :: class3
type(class2) :: myClass2
end type
type :: class4
type(class3), allocatable :: myClass3
end type
contains
subroutine copy(lhs,rhs)
class(class1), intent(out) :: lhs
class(class1), intent(in) :: rhs
lhs%ii = 7
print *, 'copy was called'
end subroutine
function constructor() result(this)
type(class4) :: this
allocate(this%myClass3)
this%myClass3%myClass2%myClass1%ii = 42
end function
end module
program main
use Classes_Header
implicit none
type(class4) :: myClass4
myClass4 = constructor()
end program
Note the similarities and differences between this and another issue I just posted. In both issues, the defined assignment is not called. Here, the nested class structure contains an `allocatable` class, while the other issue deals with a parameterized derived type.
This is still bugged in 2025.2.0.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi,
In the new example code, there is no explicit assignment ("="), something like
-----
type(class1) :: obj1, obj2
obj1 = obj2
-----
so 'copy was called' is not printed.
In the original code, the problem is caused by a combination of many factors. When you do "myClass2 = NewClass2(anActor)", the return type Class2 is not trivial as it is related to ActorContainer <- ActorContainerElement which contains a pointer ptr. Then compiler creates a copy of the object of Class2 in the buffer. When calling myClass1Container%add(myClass1), the buffer got reused unexpectedly, affecting the pointer ptr in ActorContainerElement.
A workaround could be changing NewClass2 from a function to a subroutine to avoid creating the first buffer.
-----
subroutine NewClass2(this, anActor)
class(Actor), intent(in) :: anActor
type(Class2), intent(out) :: this
allocate(this%filter)
call this%filter%subFilter%actors%add(anActor)
end subroutine
-----
Please give it a try in the larger code.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Hi Fengrui. I appreciate the response, but that is not how the Fortran standard defines assignment. Please see sections 10.2.1.3 (intrinsic assignment) and 10.2.1.4 (defined assignment) in the 2018 standard (I do not have a copy of the 2023 but assume the content is the same, though the section numbering may be different). The most important paragraph is 10.2.1.3.13:
"An intrinsic assignment where the variable is of derived type is performed as if each component of the variable
were assigned from the corresponding component of expr using pointer assignment (10.2.2) for each pointer
component, defined assignment for each nonpointer nonallocatable component of a type that has a type-bound
defined assignment consistent with the component, intrinsic assignment for each other nonpointer nonallocatable
component, and intrinsic assignment for each allocated coarray component. For unallocated coarray components,
the corresponding component of the variable shall be unallocated."
On line 50 of my most recent example, an intrinsic assignment is invoked on derived type class4. Therefore all "nonpointer nonallocatable component[s] of [that] type" should be defined via "defined assignment" if one is specified. Since class1 is a nested component of the structure, its defined assignment should be called. I agree that the defined assignment on class1 is not explicitly called anywhere, but it should be implicitly called when class4's intrinsic assignment is used on line 50. This is the bug.
Someone will probably think that defined assignment should not be called because class3 is an allocatable component. I would agree... if the defined assignment were on class3 and not class1. Per 15.4.3.4.3 note 2, defined assignment is not evoked when the left-hand side is allocatable. Fair enough. But the component myClass1 in class2 is not allocatable, so this does not apply.
The current behavior of ifx is not only inconsistent with the standard, but it is internally inconsistent with other code it produces. Defined assignment is called if I remove one class from the structure yet leave one of them as an allocatable component. Based on my understanding of the standard, ifx correctly compiles the following code which DOES call defined assignment:
module Classes_Header
implicit none
type :: class1
integer :: ii
contains
generic, public :: assignment(=) => copy
procedure, private :: copy
end type
type :: class2
type(class1) :: myClass1
end type
type :: class3
type(class2), allocatable :: myClass2
end type
contains
subroutine copy(lhs,rhs)
class(class1), intent(out) :: lhs
class(class1), intent(in) :: rhs
lhs%ii = 7
print *, 'copy was called'
end subroutine
function constructor() result(this)
type(class3) :: this
allocate(this%myClass2)
this%myClass2%myClass1%ii = 42
end function
end module
program main
use Classes_Header
implicit none
type(class3) :: myClass3
myClass3 = constructor()
end program
As for the pointer in my original code, please see another post of mine where I explain this under "additional details". Once you understand how defined assignment is supposed to operate, you will see that this is invalid behavior. If I did not have a defined assignment in that example, I completely agree that there would be a "dangling pointer". That is the purpose of the defined assignment: to perform a "deep" copy.
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Good point! The developer guide and reference also states that each component of the expression should be assigned.
That's true when the derived variable has more nested layers, the behavior becomes unexpected. I will report it as a bug to the team. Thanks!
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
Thank you, @Fengrui! I'm almost certain that fixing my updated, simplified example will solve the original problem.
Also, rereading the second sentence of my last paragraph, it sounds a bit harsh. I'm sorry; that was not my intention! Thank you for reporting this bug and the assistance. Happy 4th!

- Subscribe to RSS Feed
- Mark Topic as New
- Mark Topic as Read
- Float this Topic for Current User
- Bookmark
- Subscribe
- Printer Friendly Page