[PATCH v2 05/14] stdio-common: Introduce buffers for implementing printf
Adhemerval Zanella
adhemerval.zanella@linaro.org
Thu Jun 2 18:35:47 GMT 2022
On 02/06/2022 14:58, Florian Weimer wrote:
> * Adhemerval Zanella:
>
>> Some comments below. I am not very found on this approach, it is *another*
>> layer on our already convoluted libio/stdio code; although I don't have a
>> better solution. I *really* wish we could move away from libio...
>
> On the plus side, it completely eliminates libio for sprintf, strfromd,
> strfmon.
>
>>> +/* <printf_buffer_as_file.h> introduces a way to use struct
>>> + __printf_buffer objects from FILE * streams. To avoid storing a
>>> + function pointer (or vtable pointer) in struct __printf_buffer
>>> + (which would defeat libio vtable hardening), a switch statement
>>> + over the different flush implementations is used to implement
>>> + __printf_buffer_flush.
>>> +
>>> + __printf_buffer_mode_failed is special: it is the sticky failure
>>> + indicator. Unlike struct alloc_buffer, this is not folded into
>>> + write_ptr, so that snprintf and other string-writing functions can
>>> + discover the end of the string even in the error case, to be able
>>> + to add the null terminator. */
>>> +enum __printf_buffer_mode
>>> + {
>>> + __printf_buffer_mode_failed,
>>> + __printf_buffer_mode_to_file,
>>> + };
>>
>> Do we really need to use namespace safe names for types as well? I usually
>> think it is usually clear to use otherwise.
>
> It's needed to enable the type-generic implementations.
> stdio-common/Xprintf_buffer_flush.c uses Xprintf (buffer_mode_failed).
> I used to have two macros (Xprintf and __Xprintf), but I moved away from
> this because using __printf_ everywhere seemed more consistent.
Ok.
>
>>> +/* Marks the buffer as failed, so that __printf_buffer_has_failed
>>> + returns true and future flush operations are no-ops. */
>>> +static inline void
>>> +__printf_buffer_mark_failed (struct __printf_buffer *buf)
>>
>> For other internal interfaces it seems that current pratice is to use
>> non double underscore static inline function wrappers over the namespace
>> safe symbols. Maybe we should use the same strategy here.
>
> Same reason as above.
>>
>>> +/* Write COUNT bytes starting at S to BUF. S must not overlap with
>>> + the internal buffer. */
>>
>> Maybe add a overlpa check on Xprintf_buffer_write.c ? Not sure if it is
>> worth though.
>
> We actually can't because for sprintf, the target buffer (as specified)
> extends to the end of the address space, it's unbounded.
>
>>> +int
>>> +Xprintf_buffer_done (struct Xprintf_buffer *buf)
>>> +{
>>> + if (Xprintf_buffer_has_failed (buf))
>>> + return -1;
>>> +
>>> + /* Use uintptr_t to deal with ptrdiff_t overflows (to some degree). */
>>
>> I am not sure using uintptr_t yield any gain in fact, if
>> 'buf->write_ptr - buf->write_base' can not fit in ptrdiff_t it means the
>> code already trigger UB in write_ptr update.
>
> I should probably remove that.
>
>>> + uintptr_t written_current = buf->write_ptr - buf->write_base;
>>> + unsigned int written_total;
>>> + if (__builtin_add_overflow (buf->written, written_current, &written_total)
>>
>> I think it should use INT_ADD_OVERFLOW since we still support GCC 6.5 and
>> __builtin_{add,sub}_overflow is not reliable with GCC 6.X [1].
>>
>> [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98269
>
> We require GCC 7.1 on s390x, see 844b4d8b4b937fe6943d2c0c80ce7d871cdb1eb5.
>
> I'm not sure if INT_ADD_OVERFLOW deals correctly with mixed types.
I think it does have some drawbacks for some usages. As a side note, I wonder
if should just create another header with just what we use from intprops.h
(TYPE_MINIMUM, TYPE_MAXIMUM, INT_BUFSIZE_BOUND) so we don't need to keep with
extra unused overflow/wraps macros. And it is one *less* thing we need to
keep in sync with gnulib.
>
>>> +void
>>> +Xprintf (buffer_puts_1) (struct Xprintf_buffer *buf, const CHAR_T *s)
>>> +{
>>> + if (__glibc_unlikely (Xprintf_buffer_has_failed (buf)))
>>> + return;
>>> +
>>> + while (*s != 0)
>>> + {
>>> + if (buf->write_ptr == buf->write_end && !Xprintf_buffer_flush (buf))
>>> + return;
>>> + assert (buf->write_ptr != buf->write_end);
>>> + size_t to_copy = STRNLEN (s, buf->write_end - buf->write_ptr);
>>> + buf->write_ptr = MEMPCPY (buf->write_ptr, s, to_copy);
>>> + s += to_copy;
>>> + }
>>> +}
>>
>> Does it really need a loop here?
>
> Yes, we might reach the end of the buffer with data left to write, so we
> have to flush it. This can happen with asprintf and of course in the
> FILE * case.
>
But in this case case you will just return right? I am trying to understand
where the loop will be executed twice in this case.
More information about the Libc-alpha
mailing list