[<=2.0.5 Bug:] An unexplainable crash

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
User avatar
V2User
Posts: 195
Joined: 30 Apr 2021, 04:04

[<=2.0.5 Bug:] An unexplainable crash

19 Aug 2023, 05:44

[Moderator's note: Topic moved from Bug Reports.]

[Code1:]

Code: Select all

class CCC{
	__Init() {
		this.Ptr:=ObjPtr(this)
	}
	self=>ObjFromPtr(this.Ptr)
}
c:=CCC()
c2:=c.self
OutputDebug(ObjPtr(c2))
OutputDebug(ObjPtr(c))
This code above can return the object c itself. However, it will crash on existing, which is shown in VSCode. It seems totally unexplainable.
image.png
image.png (26.52 KiB) Viewed 1246 times
Last edited by V2User on 19 Aug 2023, 10:44, edited 1 time in total.
geek
Posts: 1053
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: [<=2.0.5 Bug:] An unexplainable crash

19 Aug 2023, 06:45

Have you been editing this post? I swear ten minutes ago you were using ObjFromPtrAddRef and I wasn't able to reproduce the issue, but now it's just ObjFromPtr. It's really easy to explain why ObjFromPtr would cause this crash, and it's entirely to do with the reference count.

Each object has a reference counter, a number that it keeps which starts at 1. Whenever a new reference to an object is made, that number is increased by one. Whenever a reference is lost (such as by going out of scope, or the variable holding it is overwritten) then the reference counter decreases by one. When that number reaches 0, all memory related to that object is released.

Because you're making a reference using ObjFromPtr without increasing the reference count, when the script is exiting it hits 0 prematurely. Specifically, when the script is exiting it will start destructing objects from the global scope that have gone out of scope. I'm not sure what order exactly, but let's imagine it starts with variable c then goes to c2.

When the script starts to destruct c, the object referenced by c will have a reference count of 1. The first step, moving the reference count to 0, will trigger AHK to call the object's __Delete() routine and then free the memory. Afterwards, it moves onto variable c2 and tries to check the reference count. That attempt to check c2's reference count will result in an access violation error because the script no longer owns that memory, it was freed.

It's super explainable, and easy to fix. When you make a new copy of an object from a pointer, you must increase its reference count. In your case, by using ObjFromPtrAddRef instead of ObjFromPtr.
User avatar
V2User
Posts: 195
Joined: 30 Apr 2021, 04:04

Re: [<=2.0.5 Bug:] An unexplainable crash

19 Aug 2023, 10:42

Thanks for your detailed answer.
geek wrote:
19 Aug 2023, 06:45
Have you been editing this post? I swear ten minutes ago you were using ObjFromPtrAddRef and I wasn't able to reproduce the issue,
Yes, I suddenly find this works well when I test for other methods which may work.
Because you're making a reference using ObjFromPtr without increasing the reference count,
Shouldn't the reference of c2's object increase immediately the first time any object is assigned to c2 from whichever function? Writing codes as usual, seems more intuitive. So now I doubt that maybe there really is something wrong with ObjFromPtr still, otherwise it should have worked at least in normal case. Currentlly, c2 surprisingly called __delete() twice in Code1, which seems unintentional.
image.png
image.png (27.81 KiB) Viewed 1058 times
Last edited by V2User on 20 Aug 2023, 02:19, edited 3 times in total.
Descolada
Posts: 1177
Joined: 23 Dec 2021, 02:30

Re: [<=2.0.5 Bug:] An unexplainable crash

20 Aug 2023, 01:09

Shouldn't the reference of c2's object increase immediately the first time any object is assigned to c2 from whichever function? Writing codes as usual, seems more intuitive. Maybe there really is something wrong with ObjFromPtr still, otherwise it should have worked at least in normal case. Currentlly, c2 is surprisingly deleted twice in Code1, which seems unintentional.
No, if you are using ObjPtr and ObjFromPtr then it is assumed you know what you are doing and are accounting for the reference count. There are situations where you might want to use the object without increasing the reference count, in which case ObjFromPtr is necessary (creating the object without increasing ref count). For example, if a DllCall returns an object from an external library, usually the library has already increased the reference count for the object and then gives ownership to you, which means you will have to decrease the ref count when you are done but not increase the count when you are starting to use it.

If you were to read the docs for ObjPtr then you'd understand what you did wrong: namely you created a pointer for an object without increasing the reference count for said object. Instead you should use either

Code: Select all

	__Init() {
		this.Ptr:=ObjPtr(this), ObjAddRef(this.Ptr)
	}
or using ObjFromPtrAddRef instead of ObjFromPtr. I think ObjFromPtrAddRef would be the preferred way, because if you want to get multiple references to the object then only one ObjAddRef isn't enough.
AHK objects decrease the ref count automatically on destruction so in this case ObjRelease isn't necessary.

You should probably start posting your bug reports in the help section.
User avatar
V2User
Posts: 195
Joined: 30 Apr 2021, 04:04

Re: [<=2.0.5 Bug:] An unexplainable crash

20 Aug 2023, 02:32

Descolada wrote:
20 Aug 2023, 01:09
No, if you are using ObjPtr and ObjFromPtr then it is assumed you know what you are doing and are accounting for the reference count. There are situations where you might want to use the object without increasing the reference count, in which case ObjFromPtr is necessary (creating the object without increasing ref count). For example, if a DllCall returns an object from an external library, usually the library has already increased the reference count for the object and then gives ownership to you, which means you will have to decrease the ref count when you are done but not increase the count when you are starting to use it.
Thank you for your answer. Perhaps how the reference counter works underneath, is really hard to exactly know.
Currentlly, c2 surprisingly called __delete() twice in Code1, which seems unintentional.
image.png
image.png (27.81 KiB) Viewed 1051 times
Descolada
Posts: 1177
Joined: 23 Dec 2021, 02:30

Re: [<=2.0.5 Bug:] An unexplainable crash

20 Aug 2023, 09:44

I would guess that __Delete is called twice because on script exit first c2 is released which means the associated objects' reference count decremented to 0 (though technically, in AHK object deletion happens when ref count is 1...). Now, when releasing c, since the ref count is already 0 it isn't decremented any more and __Delete is called again. This "unintentional" behavior most likely can be fixed, but are the extra checks on every object deletion worth the performance decrease to stop something that is assumed not to happen? Remember that this kind of situation shouldn't happen in the first place since it's assumed that the implementor takes care of keeping proper reference count when using ObjAddRef/ObjRelease...

Btw, if you wanted you could even "resurrect" objects from inside __Delete, in which case __Delete is also called twice:

Code: Select all

class AAA{
    __New() {
        this.Ptr:=ObjPtr(this)
        OutputDebug(this.Ptr "`n")
    }
    __Delete() {
        OutputDebug(this.Ptr " is being deleted`n")
        ObjAddRef(this.Ptr)
    }
}
a:=AAA()
b:=a.Ptr
a:=""
b:=ObjFromPtr(b)
OutputDebug(b.Ptr " is alive again!`n")
lexikos
Posts: 9643
Joined: 30 Sep 2013, 04:07
Contact:

Re: [<=2.0.5 Bug:] An unexplainable crash

20 Aug 2023, 20:02

An object's reference count can't ever be zero, because that should mean the object no longer exists, in which case you can't read its reference count.

AutoHotkey doesn't decrement the reference count below 1 when you Release, because it has to call __delete. __delete needs a reference to the object, which means it must have a non-zero count. If it were to increment from 0 to 1 and then back to 0 when __delete finishes, it would be "deleted" again.

If the object has already been deleted and you still have a pointer, that pointer is invalid. It points to indeterminate data. If you try to read a "reference count" from that, you could get anything.

On objects with a reference count of zero - Raymond Chen
User avatar
V2User
Posts: 195
Joined: 30 Apr 2021, 04:04

Re: [<=2.0.5 Bug:] An unexplainable crash

21 Aug 2023, 04:51

@lexikos @Descolada Thank you for your detailed answer as well as the website.
lexikos wrote:
20 Aug 2023, 20:02
If it were to increment from 0 to 1 and then back to 0 when __delete finishes, it would be "deleted" again.
So, is that to say, c2:=c.self will increase the reference counter from 0 to 1, rather than from 1 to 2? Is this by design?

Code: Select all

class CCC{
	__Init() {
		this.Ptr:=ObjPtr(this)
	}
	self=>ObjFromPtr(this.Ptr)
	__Delete() {
		OutputDebug('deleting')
	}
}
c:=CCC()
c2:=c.self
Persistent()
image.png
image.png (28.96 KiB) Viewed 819 times
20170201225639
Posts: 144
Joined: 01 Feb 2017, 22:57

Re: [<=2.0.5 Bug:] An unexplainable crash

21 Aug 2023, 05:55

lexikos wrote:
20 Aug 2023, 20:02
An object's reference count can't ever be zero, because that should mean the object no longer exists, in which case you can't read its reference count.

AutoHotkey doesn't decrement the reference count below 1 when you Release, because it has to call __delete. __delete needs a reference to the object, which means it must have a non-zero count. If it were to increment from 0 to 1 and then back to 0 when __delete finishes, it would be "deleted" again.

If the object has already been deleted and you still have a pointer, that pointer is invalid. It points to indeterminate data. If you try to read a "reference count" from that, you could get anything.

On objects with a reference count of zero - Raymond Chen
Interesting. I wonder whether a filesystem analogy would be appropriate here. From the point of view of the filesystem, as i understand it, *what it is* for a file to "exist" is nothing more than for there to exist at least one reference to a particular inode (at least one "hardlink"). So, for files, to exist is to be referred to, to go out of existence is to cease being referred to (compare: "The Egyptians believed that you die twice. Once when you take your final breath, and then again the last time someone says your name"). So the idea of a file with zero reference count is just a contradiction in terms. If all references (hardlinks) to a file are removed, there may still exist at the inode the same sequence of 1s and 0s that once constituted an actual file, but that file itself no longer exists, all the 1s and 0s left behind at that inode is just the "remnants" of a file.
lexikos
Posts: 9643
Joined: 30 Sep 2013, 04:07
Contact:

Re: [<=2.0.5 Bug:] An unexplainable crash

21 Aug 2023, 22:01

V2User wrote:So, is that to say, c2:=c.self will increase the reference counter from 0 to 1, rather than from 1 to 2?
No.

I literally wrote "An object's reference count can't ever be zero", and you are asking whether this code will increase the reference count from 0. It can't do that, because it is never 0.

After c:=CCC(), there is 1 reference, in c. c.self creates another reference, while leaving the count at 1. That ObjFromPtr does not increase the reference count was already explained to you. When either reference is released, the object is deleted. When the program attempts to release the second reference (or invoke the object in any way), the behaviour is undefined, because the reference no longer points to a valid object.

Return to “Ask for Help (v2)”

Who is online

Users browsing this forum: diegg1739, Frogrammer, wilkster and 73 guests