µUBSan: clean-room reimplementation of the Undefined Behavior Sanitizer runtime
blog.netbsd.org
blog.netbsd.org
- The author wanted the personal challenge for the task
- The author wanted the code licensed under a BSD 2-clause and felt the existing license didn't fit her/his ideals.
Of course, I could just be misunderstanding, and they could be using "clean-room" as a synonym for "from scratch", rather than the meaning I linked to.
Is the runtime API shared between both GCC and LLVM/Clang?
I don't really get why people do this. Linking is one of the easiest and most broadly supported features of C environments on every platform.
What gives you the impression that this is the "wrong" way to do this? How many beginners are going to be looking to μUBSan for guidance on how to structure their generic library? And it's not like linking isn't involved, you still end up linking the one object that is created from that source file (unless you #include it, which would also probably work).
It's far less maintainable and goes against the grain of how people expect libraries to behave.
>How many beginners are going to be looking to μUBSan for guidance on how to structure their generic library?
Any beginner who uses it. You make a good point, though, this isn't exactly a beginner-tier tool so why is it being distributed like one?
>unless you #include it, which would also probably work
I think that's what they're expecting you to do. And if you're linking anyway, what does it matter if it's one object or several? Or more realistically, a single archive (built from many objects which are themselves built from many source files)?
How so? What form of maintenance does it prevent or hurt?
> ...and goes against the grain of how people expect libraries to behave.
You could dynamically link it if you wanted, it's just that there is little reason to. This approach is inclusive: it enables all three major ways people use libraries.
Honestly, I don't see what the problem is. You're entitled to prefer dynamic linking or static linking, but how does it hurt you that it can also be used another way?
>I don't consider open source projects a black box, I evaluate every project under the lens of someone who expects to someday have to work with the code myself and send patches upstream.
And I think I'm squarely in the target audience for this tool anyway. What makes you think it's a feature for someone else?
The fact that they mention it specifically and proudly on the front page indicates to me that the author, and the NetBSD maintainers who accepted it, consider it good that it is a single, largely self-contained source file. It is only ~1300 source lines, so I don't see why it should necessarily be split out. Something tells me the original authors and the ongoing maintainers of this file have a better idea what value this choice represents than you do (since you haven't said anything particularly compelling in favour of splitting this into several files and objects).
The basics aren't complicated, but doing it correctly requires more than the basics IME.
Each platform does it slightly differently, and I support multiple platforms. I've repeatedly seen static libs linked into multiple dynamic libs and cause all kinds of trouble because there's multiple copies of global data, of which only one was probably initialized correctly. I have seen multiple different stdlibs successfully linked into the same program, causing ABI mismatches that compiled, linked, and "usually" ran at runtime - but had some nasty crashes to be debugged, because std::vector<int> was an entirely different type at the call site vs the callee's implementation. Version mismatches of other SDKs are also common. I've had all kinds of weird constraints on what compilation and link flags I could use based on what static libs I've linked (e.g. being unable to link with exception handling enabled because one of my dependencies was built with it disabled)
Additionally you don't have libs for all my platforms (or sometimes any of them), so I have to build from source anyways, so I have to write build rules for your lib because the defaults probably don't work, and because build rules are never documented I have to mostly reverse engineer intent to do so - possibly without a viable build in the first place if your only working libs are for platforms I don't use.
Or if I'm lucky enough that reusing your build rules is viable, I have to try and sync various flags between my projects written in one build system and your projects written in yet another build system, and prevent these from getting out of sync for the remainder of the lifetime of the project. I likely have to jump through all kinds of stupid hoops like installing the exact right version of python and installing undocumented but required packages just to run the configuration scripts that drive the build.
...or for single-source libs, I can probably just drop a single file into one of my existing projects and have everything work. Okay, so some of that was just because they actually cared about minimizing how painful it is to integrate their library at all, but having a single .c file helped too.
I'd only write a single .c library for the simplest of libraries - or as an automatically generated file - but it can help.
Besides, in this scenario, it may not be viewed as a library a user should manage. The only benefit I can think of from linking is getting security patches without recompiling. That might easily not be a priority. Why is that a problem?
#ifndef __onefilelib__
#define __onefilelib__
#include "onefilelib.c"
#endif
(Also potentially defining "main" as something else, if it happens that the "onefilelib.c" has an entry point for some reason.)No, it's absolutely not easier. And the more you deviate from amd64 linux and gcc, the harder it gets.
What harm is it to you that it's a single source file and a single object?
I don't consider open source projects a black box, I evaluate every project under the lens of someone who expects to someday have to work with the code myself and send patches upstream.
To me, one huge file or many small ones doesn't matter all that much because I have cscope on my side. What matters is being able to find things quickly. Still, one huge file means I can asterisk on a symbol in vim to quickly find all references to it without having to switch to cscope -- that's not nothing.
Also, https://github.com/NetBSD/src/blob/trunk/common/lib/libc/mis... is just not that big... only 1639 lines, 1310 sloc. Compare to, let's say, OpenSSL, where 29 C source files have more lines than ubsan.c, with several being more than twice the size. Maybe you think OpenSSL is not a fair example because there's lots of tables and what not? Even if you look at files outside crypto/, you'll find lots of big ones. Just for fun I looked at a variety of other open source projects: Heimdal, MIT Kerberos, glibc, PostgreSQL -- you'll be shocked when you look at their file sizes, even when you elide files that are obviously mostly-data.
Source file size is not that interesting. The contents is. When a set of sources is small enough, organizing it into multiple files is not necessarily a win. 1.3kloc doesn't seem like that big a file.
Complex features usually necessitate modularization, which is against the idea of the single file library. Modularization in c and c++ is very poor and based on having multiple files, some of which represent the interface and others that represent the implementation. I at least partly think this is his objection.
> > I've decided to write the whole µUBSan runtime as a single self-contained .c soure-code file, as it makes it easier for it to be reused by every interested party.
> I don't really get why people do this. Linking is one of the easiest and most broadly supported features of C environments on every platform.
Sure, this is true, and users already have to know how to link, unless they are #include'ing this, but still, a single file is much easier to share/distribute and use, and in any case, splitting up such a small file (by the standards of.. a number of open source projects I looked at, it's small) seems unnecessary.
I was just trying to explain what I thought his argument was against them, since I don't think he really explicitly stated it outside of generally speaking of maintenance issues. He just said he would prefer to contribute patches to a project that had multiple files and a makefile based build system.
I was assuming his argument was about the future directions the project could go, which I can see as being a valid criticism. As I said the main issue with a single file implementation is that potential users may end up using parts of the implementation instead of just the public facing interface you would like them to use.
Personally files this large become just very annoying overall. Code review tools tend to become slow on them (e.g. Gerrit is super-slow with files that are quite a bit smaller than qwidget.cpp), navigation is only possible via symbols, the import / include area becomes a huge mess (which also tends to generate constant merge conflicts, because imports are a common hot area for writes) etc.
LTO is awesome, as some code does benefit from optimizing for a minute to get 5% more speed for the next week (and sometimes even more aggressive parameters for the optimizer, if you run the code for long enough to matter).
And build systems are one of the most varied features of C environments.
I've seen large projects (e.g. OpenSSL) take weeks of effort to integrate into build systems. Suffice to say, no one wants to try and merge patches in afterwords.
Nevermind the number of source trees I've seen with copies of some old version of OpenSSL source, ugh.
Reasons why people like single file libs:
* Easy for a beginner to use.
* No non-obvious dependencies. (check includes to see them) * Easy to repackage for multiple OS's
* Easy to understand since each reference is in the same file.
* Typically simpler and have fewer features(which can help one focus on the main issues.)
* No need for either static or dynamic linking, although it is basically the same as static linking in some ways.
* Eliminates the need for dependency management.
Reasons against single file libraries:
* No isolation of components. (The interface and implementation are in the same file)
* Adding new feature may make the library too complicated(which can lead to some features being denied)
* A bit harder to maintain. Since the interface and implementation are the same, some users may depend on internal interfaces. Which can inhibit changes needed for performance.
* May encourage bad behaviour, since the user need never learn to link against a third party library. If the culture changes enough there may be many people contributing who are not able to use standard tools in the standard way. (The idea that beginners should learn the culture that is used by the toolmakers.)
Hopefully this is useful for someone, and I hope I did not misrepresent any opinions.[Also huge fan of Drew]