crystal icon indicating copy to clipboard operation
crystal copied to clipboard

Allow mixing of integers and pointers in `Crystal::System.print_error`

Open HertzDevil opened this issue 1 year ago • 11 comments

When an external library calls Crystal::System.print_error with an argument, we do not have control over the argument type; in the case of Boehm GC, that argument is always an unsigned integer, even though the actual format specifier might be %s or %p. This PR makes those argument types interchangeable. (An integer or pointer is interpreted as the address to a null-terminated string for %s, never the address of a Crystal String.)

Also uses %p consistently in our own calls to .print_error.

HertzDevil avatar Dec 28 '23 16:12 HertzDevil

Making pointer and integers interchangeable seems like a good idea. That should be safe and sound.

But I'm worried about treating arbitrary integers as string pointers. This is potentially unsafe and can easily crash the process.

I'm not sure if there's a different way to enable the use case with libgc. Perhaps at least we can restrict %s to work only for the integer type that corresponds to the size of a pointer address? (~ SizeT)

straight-shoota avatar Jan 03 '24 12:01 straight-shoota

Also I think the refactoring part should be extracted as a separate PR. It's indendent of the designator type related changes.

straight-shoota avatar Jan 03 '24 12:01 straight-shoota

I don't think restricting the integer type makes the function any safer, because that implies a C library has to indicate the impossibility of pointers by using a 64-bit type on 32-bit systems but a 32-bit type on 64-bit systems.

HertzDevil avatar Jan 04 '24 07:01 HertzDevil

IMO indicating the impossibility of a pointer is not that relevant. If a lib wants to ensure proper usage, they should prevent improper usage of the designator. My goal would be to make the margin of error as tight as possible. For example, an 8-bit integer type should never be expected to be an address. Or a signed integer type.

straight-shoota avatar Jan 04 '24 10:01 straight-shoota

That only affects our own calls to .print_error, which are not the focus of this PR. LibGC::Word is going to be the same whether we allow all integer types or just unsigned pointer-sized ones.

HertzDevil avatar Jan 04 '24 10:01 HertzDevil

Calls from Crystal land might not be the focus of this PR, but it's a use case of this method. And this change indiscriminately enables %s for Int::Primitive. So I think it affects our own calls as well. Unless I'm missing something here?

straight-shoota avatar Jan 04 '24 10:01 straight-shoota

I honestly don't see a problem about that.

HertzDevil avatar Jan 04 '24 11:01 HertzDevil

My concern is about the following program:

Crystal::System.print_error "%s", 1_i8

On master, it prints (???) which I think is correct and expected because an Int8 cannot be a valid address in any realistic scenario (except for zero/null value maybe). With this patch, it triggers an invalid memory access. That would be avoidable if the type filter was more restrictive than Int::Primitive.

straight-shoota avatar Jan 04 '24 11:01 straight-shoota

Exactly. I don't see a problem with allowing Int::Primitive here. Otherwise the caller would simply add to_u64 at the call site and that doesn't make the .print_error call any safer.

HertzDevil avatar Jan 04 '24 11:01 HertzDevil

Yeah, but then it would be explicit. 🤷 Of course we cannot prevent accidental submission of random UInt64 numbers. But we can prevent anything that's not a UInt64 from being erroneously interpreted as a pointer.

straight-shoota avatar Jan 10 '24 22:01 straight-shoota

You have to include UInt32 since native pointers have that size on 32-bit targets. The most I'd allow is Int32 | UInt32 | Int64 | UInt64 on all platforms, since there is no guarantee C libraries would give us unsigned values.

HertzDevil avatar Jan 11 '24 12:01 HertzDevil