Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ImageDataUtil.copyPixels crash on Hashlink #1875

Merged
merged 6 commits into from
Dec 16, 2024

Conversation

ACrazyTown
Copy link
Contributor

On C++ when a passed Vector2 instance is null, it'll just make it (0, 0) on the C++ side:

if (!val_is_null (vec)) {
x = val_number (val_field (vec, id_x));
y = val_number (val_field (vec, id_y));
} else {
x = 0;
y = 0;
}

Unlike C++, on Hashlink there's no failsafe for if the Vector2 instance is null, it'll just pass it further to ImageDataUtil::copyPixels causing an access violation. This PR checks if we're passing in a null Vector2 on the Haxe side, and makes a new object if we are. I assume this is better than making it on the C++ side but please correct me if I'm wrong

Fixes: openfl/openfl#2754

@joshtynjala
Copy link
Member

Both hxcpp and HashLink share Lime's C++ code, so I would prefer if null checks for both targets were consistently all in C++ or all in Haxe.

Looking at some other C++ code in Lime, I suspect that a null pointer will be 0 for HashLink.

Maybe something like this is all that's needed?

if (vec && !val_is_null (vec)) { 

@ACrazyTown
Copy link
Contributor Author

I'm confused because Lime only seems to make C++ objects when compiling for C++/native:

} else {
Image _alphaImage = Image (alphaImage);
Vector2 _alphaPoint = Vector2 (alphaPoint);
ImageDataUtil::CopyPixels (&_image, &_sourceImage, &_sourceRect, &_destPoint, &_alphaImage, &_alphaPoint, mergeAlpha);
}

When targeting Hashlink it just directly passes the arguments from Haxe:
HL_PRIM void HL_NAME(hl_image_data_util_copy_pixels) (Image* image, Image* sourceImage, Rectangle* sourceRect, Vector2* destPoint, Image* alphaImage, Vector2* alphaPoint, bool mergeAlpha) {
if (!alphaImage) {
ImageDataUtil::CopyPixels (image, sourceImage, sourceRect, destPoint, NULL, NULL, mergeAlpha);
} else {
ImageDataUtil::CopyPixels (image, sourceImage, sourceRect, destPoint, alphaImage, alphaPoint, mergeAlpha);
}
}

Where would your fix go? I thought about checking if the pointer was null on the C++ side and then making an object there but I'm not completely sure if that has any negative effect? Would that have to get manually freed since it was made on the C++ side?

@joshtynjala
Copy link
Member

If Vector2.cpp isn't the correct place, that's totally fine. I made sure to say "Maybe something like" to be clear that I was not suggesting exactly what you need to do. I haven't worked on that particular code, so you are probably more familiar with how it works than I am at this moment.

Perhaps you could add a check in hl_image_data_util_copy_pixels. Maybe something like this:

if (!destPoint) {

     destPoint = Vector2(0, 0);

}

@joshtynjala
Copy link
Member

Note: I don't frequently work with C++, and I did not try that code through a compiler. As before, it's more to give you an idea of where to look, and you can fiddle with getting the right syntax.

I just realized that it's probably a pointer, so maybe more like:

if (!destPoint) {

     destPoint = new Vector2(0, 0);

}

And then you probably need to delete it after, if it wasn't passed in from Haxe.

@ACrazyTown
Copy link
Contributor Author

If Vector2.cpp isn't the correct place, that's totally fine. I made sure to say "Maybe something like" to be clear that I was not suggesting exactly what you need to do.

Sorry, I wasn't exactly sure what you meant so just wanted to clarify.

I pushed a commit where the function will now allocate a temporary Vector2 object (this is what the hxcpp version does as well) that gets used if the passed Vector2* from Haxe is null. It'll get automatically cleaned up when it goes out of scope.
This is a bit more simpler than managing pointers

@joshtynjala
Copy link
Member

That looks like we're on the right track. If I may suggest one last tweak, I think that we should avoid allocating tempPoint when it's not needed.

This should probably work:

Vector2* _alphaPoint = alphaPoint;

if (!_alphaPoint) {

	Vector2 tempPoint = Vector2(0, 0);
	_alphaPoint = &tempPoint;

}

@ACrazyTown
Copy link
Contributor Author

Should be good to go now

@trnzk
Copy link

trnzk commented Dec 12, 2024

This kind of usage can cause issues because you’re assigning the address of a local variable (tempPoint) to a pointer. Once the block ends, the local variable goes out of scope, leaving the pointer pointing to invalid memory, which can lead to undefined behavior.

@player-03
Copy link
Contributor

I'd be worried about that too.

@ACrazyTown
Copy link
Contributor Author

This kind of usage can cause issues because you’re assigning the address of a local variable (tempPoint) to a pointer. Once the block ends, the local variable goes out of scope, leaving the pointer pointing to invalid memory, which can lead to undefined behavior.

So should I just set the pointer to nullptr? Or alternatively do something like this:

if (!alphaImage) { 

    ImageDataUtil::CopyPixels (image, sourceImage, sourceRect, destPoint, NULL, NULL, mergeAlpha); 

} else { 

    if (!alphaPoint) {

        Vector2 tempAlphaPoint = Vector2(0, 0);
        ImageDataUtil::CopyPixels (image, sourceImage, sourceRect, destPoint, alphaImage, &tempAlphaPoint, mergeAlpha); 

    } else {

        ImageDataUtil::CopyPixels (image, sourceImage, sourceRect, destPoint, alphaImage, alphaPoint, mergeAlpha); 

    }

}
 	

@joshtynjala
Copy link
Member

Or alternatively do something like this

That seems good to me.

@trnzk
Copy link

trnzk commented Dec 12, 2024

I think it was fine except for the last commit. Vector2 tempPoint(0, 0); uses the stack and I believe it has negligible overhead considering what the function does.

@ACrazyTown
Copy link
Contributor Author

Do I roll back to 824da26 or do I proceed with the suggestion I made earlier? 😅 Not sure where to go from here...

@joshtynjala
Copy link
Member

Go with this one:

if (!alphaImage) { 

    ImageDataUtil::CopyPixels (image, sourceImage, sourceRect, destPoint, NULL, NULL, mergeAlpha); 

} else { 

    if (!alphaPoint) {

        Vector2 tempAlphaPoint = Vector2(0, 0);
        ImageDataUtil::CopyPixels (image, sourceImage, sourceRect, destPoint, alphaImage, &tempAlphaPoint, mergeAlpha); 

    } else {

        ImageDataUtil::CopyPixels (image, sourceImage, sourceRect, destPoint, alphaImage, alphaPoint, mergeAlpha); 

    }

}

@joshtynjala joshtynjala merged commit 45505f4 into openfl:develop Dec 16, 2024
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

BitmapData: copyPixels crashes on HL
4 participants