Page MenuHomeFreeBSD

libc: Implement mbrtoc8() and c8rtomb() as per C23
Needs ReviewPublic

Authored by kfv on Thu, Aug 27, 1:18 PM.
Tags
None
Referenced Files
F171261577: D59213.id185205.diff
Wed, Sep 9, 10:45 PM
F171228221: D59213.id185205.diff
Wed, Sep 9, 4:18 PM
F171211347: D59213.id185205.diff
Wed, Sep 9, 1:39 PM
Unknown Object (File)
Tue, Sep 8, 7:03 PM
Unknown Object (File)
Tue, Sep 8, 4:48 PM
Unknown Object (File)
Tue, Sep 8, 2:36 AM
Unknown Object (File)
Tue, Sep 8, 1:11 AM
Unknown Object (File)
Mon, Sep 7, 4:43 AM
Subscribers

Details

Reviewers
dteske
fuz

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Skipped
Unit
Tests Skipped
Build Status
Buildable 76659
Build 73542: arc lint + arc unit

Event Timeline

kfv requested review of this revision.Thu, Aug 27, 1:18 PM

I’ll take care of manpages in the coming days.

This seems to be correct at first glance, but I think it can be greatly simplified and improved in performance.. I did a lot of research on Unicode validation and transcoding and had originally meant to take a stab at this function, but then forgot about it.

I'll think about it some more and then let you know what I came up with.

For example, state machines with lookup tables can be used instead of cascades of if-statements. They are much faster in the scalar case.

include/uchar.h
35

I had this symbol as C23 only for a reason. I think it was because things go really wrong in C++ land if this header defines char8_t otherwise. Please check this carefully (e.g. by building world with the patch applied) or just leave things as they were.

lib/libc/locale/c8rtomb.c
22

It would be great if the design patterns and symbol naming could be similar to the existing files c16rtomb.c and c32rtomb.c for consistency.

26

That 64 is a magic number. Where is it from? Is there a macro we can use instead?

94

I don't think this comment is relevant. \0 is the string terminator, which does not end up being transcoded. The function is meant to be restartable, meaning, that you can deliver input in chunks that may not themselves all start or end at boundaries of full characters (if that wasn't the case, then the mbstate_t argument would be pointless, as you'd always be in initial state when calling this function).

lib/libc/locale/mbrtoc8.c
23

What is the meaning of these structure members?

This seems to be correct at first glance, but I think it can be greatly simplified and improved in performance.. I did a lot of research on Unicode validation and transcoding and had originally meant to take a stab at this function, but then forgot about it.

I'll think about it some more and then let you know what I came up with.

That'd be great, I appreciate it.

For example, state machines with lookup tables can be used instead of cascades of if-statements. They are much faster in the scalar case.

DFA or class table is a good fit for bulk UTF-8, but these calls only handle one unit at a time and then go through mbrtoc32/c32rtomb, which dominate. The remaining compares are the ASCII/char32_t length split and the Table 3-7 second-byte checks, so I would expect little gain from a lookup table here, and it would also diverge from utf8.c. But I might be wrong, so I would be happy to discuss it further.

include/uchar.h
35

What we do now is the same idea as char16_t: _types.h sets _CHAR8_T_DECLARED when the language already has the type, and uchar.h only typedefs if that flag is clear. We key it off __cpp_char8_t, not __cplusplus >= 202002L, so -fchar8_t / -fno-char8_t are correct. Prototypes are emitted in C++ only when __cpp_char8_t is set, so they always use the builtin, never unsigned char pretending to be char8_t.

C++11/17 without the builtin do get typedef unsigned char char8_t if they include <uchar.h>. That cannot collide with a keyword (there isn’t one). It can collide with a user identifier named char8_t, same class of issue as char16_t before C++11. The keyword case is protected.

So, in fact, by improving it slightly and guarding against __cpp_char8_t, I tried fixing a rare bug that had been lurking there for a while. I ran pretty much every test that sprang to my mind, and I think this is considerably safer than the previous check. Bu I would really appreciate it if you could take a look as well and let me know if you spot anything I’ve missed.

lib/libc/locale/c8rtomb.c
22

Sure, I can rename _C8rtombExtra / _Mbrtoc8Extra to _Char8State.

26

It’s half the size of the mbstate_t blob, and I think that’s an OK promise. Perhaps I could do this:

#define _MBSTATE_CONV_PREFIX (sizeof(mbstate_t) / 2)

I’m still not sure it’s the best we can do, though. I’d rather use sizeof(extra) + sizeof(_ConversionState) <= sizeof(mbstate_t), but that would pull in the iconv types, and our case here is MK_ICONV=no.

What would you do, Robert?

94

But u8'\0' is a char8_t value, not a C-string terminator. Also, restartability remains unchanged. If the comment is misleading, we can certainly drop it altogether (or perhaps just the second sentence). However, if I’m missing something here, I’d appreciate it if you could point it out and correct me.

lib/libc/locale/mbrtoc8.c
23

nleft / next / buf are the leftover UTF-8 units after we already returned the first one ((size_t)-3), and incomp means mbrtoc32 last returned (size_t)-2, so a following NUL is EILSEQ instead of being swallowed by iconv.

Document mbrtoc8() and c8rtomb() functions

I'll have some time this weekend (hackathon) to look at this change set and try to find a better implementation.

lib/libc/locale/c8rtomb.c
94

Yeah ok I guess that makes sense. I mixed up strings and characters while reading this one; it was late.

lib/libc/locale/mbrtoc8.c
23

Great! Please add comments documenting this meaning so others have an easier time following your code.

I've rewritten c8rtomb() using Björn Höhrmann's algorithm. I'll send you an e-mail with the details shortly.
Right now I am somewhat stumped about the stupidity of our locale implementation and will have to think about it some more.

lib/libc/locale/Makefile.inc
30

This needs to have an _iconv version for when we use the MK_ICONV codepath. This code is very strange.

lib/libc/locale/mbrtoc8.c
112

You can inline this function and call the underlying primitive directly to save time.
We can assume we are in the non-iconv variant of libc if this code is built; you'll need to provide an _iconv variant for the case when iconv is enabled.

Default MK_ICONV=yes now runs mbrtoc8/c8rtomb through Citrus like c16/c32, and unsurprisingly, we now inherit its bugs as a result: null s resets, ill-formed input often comes back as (size_t)-2, 5-/6-byte UTF-8 (as with RFC 2279 §2—obsoleted by RFC 3629 §3) and values above U+10FFFF are accepted, U+FEFF fails, and each mbstate_t leaks. Those need an iconv fix, though, so not c8 special cases. FWIW, WITHOUT_ICONV c8rtomb stays C23 in (c8rtomb.c / mbrtoc8.c), and mbrtoc8 matches mbrtoc16.

Done to maintain consistency with the existing implementation, as discussed with @fuz.

fuz requested changes to this revision.Mon, Sep 7, 10:49 PM

Restore the copyright header and please have a look at the other things, too.

lib/libc/locale/c8rtomb.c
5

Please restore the copyright to how it was when I sent you the file.
We do not change external copyright headers. The line

See <https://bjoern.hoehrmann.de/utf-8/decoder/dfa/> for details.

is part of the author's copyright header and MUST be preserved.

49

There was no need to prefix these. They were fine as they were before. Also you removed my comments.

77

Why did you fuck up the formatting of these? It was better before.

112

I guess that's a reasonable option to permit continuation following an encoding error.

lib/libc/tests/locale/c8rtomb_test.c
31

Please use ATF_REQUIRE_EQ for all of these.

This revision now requires changes to proceed.Mon, Sep 7, 10:49 PM
lib/libc/locale/c8rtomb.c
22

The state does not need to be a uint32_t. And unsigned integer type is fine and it's a good idea to have the platform pick a good one. I changed it to unsigned for this reason. Why did you undo the change?

sys/sys/_types.h
212–213

This appears to be unrelated?

Sorry if I made some modifications to what you sent me via email. I actually thought I was allowed to, as it isn't finalised yet and we're working through it together. I'm just trying to make sure we're not rushing things and that we're on the same page as we go along, for future maintenance and stuff we'd be working on together. I certainly don't mean to be rude or inattentive. Also, thank you for putting so much time and effort into reviewing everything, explaining things, and discussing it all with me, and being this much receptive and kind throughout. I really appreciate it.

I'll fix the screwed-up formatting, take care of the ATF_REQUIRE_EQ, and revert the state/type changes that I carelessly made. For the rest, please have a look at my replies and let me know what you'd like me to do next.

lib/libc/locale/c8rtomb.c
5

Right, we do not, but take a look at this section. Also, the MIT licence only requires the copyright notice and the permission notice (where for the latter we only use the SPDX short identifier).

Excerpt from the MIT licence:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

Besides, IMHO, going with a dual licence could be preferable, keeping both the standardised BSD-2-Clause for our new c8rtomb.c and the MIT licence for what we have imported. And for that I chose AND (although you had recommended OR) owing to the following excerpt from FreeBSD Licensing Policy:

OR should be used if the file has a choice of license and one license is selected. For example, some dtsi files are available under dual licenses:

// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause

AND should be used if the file has multiple licenses whose terms all apply to use the file. For example, if code has been incorporated by several projects, each with their own license:

// SPDX-License-Identifier: BSD-2-Clause AND MIT

So, to my understanding (and limited experience), URLs and/or any additional data provided, as long as they are not part of the copyright notice per se, seem to be fine to omit. I would really appreciate it if you could take another look and see if there is a chance I might be right here.

22

Well, I'm sorry, it was my oversight, I honestly missed your valid rationale for this. I just followed Höhrmann's use of uint32_t for no particular reason. I'll apply yours shortly. Thanks for explaining this, by the way.

49

Right. I picked these from Björn Höhrmann's post and thought they'd be descriptive enough without any comments or further explanation. Let me know if you'd like me to revert this change as well.

77

Yikes! It was just personal taste, looked cleaner to my eyes actually. Righto, I’ll revert it now :-))

112

Thanks. Leaving REJECT absorbing would make every subsequent call fail until the caller clears ps, whereas resetting to ACCEPT would let the next unit start a new sequence. So, since C23 §7.30.1.2 leaves the state after EILSEQ unspecified, I went with the latter, so that a single bad unit doesn’t poison the rest of the stream.

lib/libc/tests/locale/c8rtomb_test.c
31

Righto, will do!

sys/sys/_types.h
212–213

Nope. Quite the contrary, I consider it very important and related, and it has been here since my first diff. I told you about this here.

Address the three promised review points

You are allowed to make changes, and yes we're working on this together.
It's just that I already spent time adapting the code to our conventions, so you changing it all back seems strange.

We're slowly getting towards a workable patch set!

lib/libc/locale/c8rtomb.c
5

Sorry, I'm not going to budge on this one. Please put the “See also” back in. It's clearly part of the copyright notice. And even if it was not, it's really rude to remove this sort of stuff. If you look through files we adapted from other projects, you see that we generally keep the original copyright headers as they are, just adding our own stuff. We never remove existing copyright lines from files. Apart from often being a licensing requirement, this is the least we can do to respect the author's rights and wishes to be credited for their work.

In particular in German law, the author has the right to decide how he wishes to be credited (§ 13 UrhG). The author has clearly expressed his wish to be credited with these two lines and we should respect that.

As for the AND/OR thing, yours is right, I probably mixed it up due to sleep deprivation.

22

It's fine, don't worry about this. I went through the code before I sent it to you, reviewed it, and did some light copy-editing. So I was a bit surprising to see some of the changes I did reverted. In particular, I don't like how authors often use fixed-width types for no particular reason, so I changed the use to a platform-specific type to let the platform pick a good type.

Fixed-width types have a documentation signal. They say “this variable must have this width or it won't work.” If you use them when the variable does not in fact have to have this particular width, you're creating a “Chesterton's fence” during any sort of rework, wasting people's time. So I try to use fixed-width types only if they can be justified. Otherwise native types (int/long/...) or types like int32least_t may be more useful to document intent.

49

I mean it's probably fine without a comment, but the comment certainly help people get the intent in the first place. I am kind of puzzled why you went the extra mile to remove them.

As for the comment you put above it, I don't think it is super useful as it views the decoding from a less important angle. The most important bit is that the table parses UTF-8. It rejects all invalid UTF-8, so it's unclear why you mention only a few of the possible encoding errors you can do (as if it would not catch other issues, like stray or missing follow bytes?) The second thing is that the character class itself is used to strip off the UTF-8 tag bits, which makes this decoder much simpler than others. And now with the original comment restored, the rest of it is kind of redundant.

112

Yes indeed, good idea.

sys/sys/_types.h
212–213

Right sorry, I forgot. I was late at night yesterday and I was tired.

lib/libc/locale/c8rtomb.c
5

Humble suggestion, certainly not legal advice: MIT and BSD-2-Clause are completely equivalent and there is absolutely zero to be gained from increasing the complexity of the legal code by dual licensing this. Already we are debating the difference between AND and OR and additional man hours will continue to be wasted on this issue in the future for no benefit in a "<permissive> {and | or} <permissive>" case. From FreeBSD policy side, it's not a special case for it to be MIT.

Personally, I really prefer BSD-2-Clause for new files in FreeBSD for simplicity and I also prefer leaving it at MIT if you're bringing in something that's MIT for the exact same reason. Again, just a suggestion.

lib/libc/locale/c8rtomb.c
5

OK, I’m convinced. It was just that the licence section in there, with no “See also”, made me think we could remove it. But alright, I’ll take care of it sharpish. Roger that.

22

Nicely put, agreed.

49

Right. I wanted to move the “See also” and thought I could perhaps bring it here, but, well, it didn’t work out nicely. Reverting it to your style.

And on the surprise this and the revert above caused, my apologies, Robert. I tend to revise a patch over and over while working through it, and occasionally make edits without a note explaining why. Not a comment on the edit itself, just an artefact of how I lean toward minimalism and iterative improvements, and not always for the better, admittedly. So please poke me if it ever happens and I'll explain myself.

Thinking about it more, I should've submitted the original version with just my updates, and flagged subjective changes like this as inline comments here instead so we could discuss. Next time ;-D

sys/sys/_types.h
212–213

No worries at all, mate.

lib/libc/locale/c8rtomb.c
5

That makes sense. I’m convinced here as well. My preference for keeping the BSD-2-Clause, for consistency with the standardised and preferred licence, was really just a matter of taste, so largely subjective. OK, I’ll go with MIT, as initially done by Robert. Thanks a lot for your review, Alexander.

Address the remaining review points

When you scroll past the "Revision Contents" section and get into the Diff, there is a comment tracker on the right of the banner. Right now it reads "17 / 39" and that helps track comments that are marked done. Right now it says 17 done because your own comments automatically get marked done. 39 minus 17 is 22.

As part of good review hygiene, I'd like you to work on getting in the habit of marking-done the comments from other people after they have been handled, this will help drive the tracker to 100% complete which as a reviewer, I myself track in my own reviews as practice of this good hygiene to:

  1. Enter into the chronological sequence when inline comments are done.
  2. Leave-open and NOT marked done any series of inline comments that are still under discussion
  3. Ensure that before I land a review, I've honestly handled all the inline comments (this feeds into the next point)
  4. Making sure nobody feels like their feedback was ignored when a review lands

Right now I see several inline comments from @fuz that can be marked done.

As a side note, as a review gets worked on more and more, the inline comments get shifted around and if it is marked done, it saves the hassle of having to drive into the rewind button to view the code as it was when the comment was left. This is not your's or anyone's fault per-se, it's just a natural complexity of Phabricator. Which ultimately adds to the value in marking inline comments done as you go.

It also just feels really good to mark a series of inline comments done. Just don't forget to click submit at the bottom of the page after you mark them done (otherwise nothing happens). A comment during submission that marks inline comments as done is entirely optional.

Thanks for your notes! Sure, I’ll keep them in mind and take care of those herein in a minute.

kfv marked 17 inline comments as done.Tue, Sep 8, 6:38 PM

Mark several review notes as resolved: Björn Höhrmann’s DFA implementation, __cpp_char8_t rationale, u8'\0' clarification, c8rtomb licensing, data type improvements, the states enum, urtf8d table formatting, and unit test improvements.

kfv marked 3 inline comments as done.

Rename _C8rtombExtra to _Char8State for consistency

kfv marked an inline comment as done.Tue, Sep 8, 8:52 PM

Mark review comment on _Mbrtoc8Extra as done, as it is no longer applicable following the changes that were implemented.

lib/libc/locale/c8rtomb.c
26

I’ll drop this entirely for now; we can add something more accurate later.

kfv marked an inline comment as done.

Drop sizeof(mbstate_t) - sizeof(_Char8State) >= 64 for now