diff options
-rw-r--r-- | .clang-tidy | 3 | ||||
-rw-r--r-- | flake.nix | 18 | ||||
-rw-r--r-- | meson.build | 15 | ||||
-rw-r--r-- | package.nix | 25 | ||||
-rw-r--r-- | releng/README.md | 2 | ||||
-rw-r--r-- | releng/create_release.xsh | 19 | ||||
-rw-r--r-- | releng/version.py | 1 | ||||
-rw-r--r-- | src/asan-options/asan-options.cc | 17 | ||||
-rw-r--r-- | src/libexpr/eval.cc | 9 | ||||
-rw-r--r-- | src/libexpr/value.hh | 398 | ||||
-rw-r--r-- | src/libutil/concepts.hh | 22 | ||||
-rw-r--r-- | src/libutil/meson.build | 1 | ||||
-rw-r--r-- | src/meson.build | 11 | ||||
-rw-r--r-- | src/nix/meson.build | 1 | ||||
-rw-r--r-- | tests/functional/repl_characterization/meson.build | 1 | ||||
-rw-r--r-- | tests/functional/test-libstoreconsumer/meson.build | 1 | ||||
-rw-r--r-- | tests/unit/meson.build | 17 | ||||
-rw-r--r-- | version.json | 1 |
18 files changed, 530 insertions, 32 deletions
diff --git a/.clang-tidy b/.clang-tidy index 3b5dcd91a..0cc1f2520 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -16,3 +16,6 @@ Checks: - -bugprone-unchecked-optional-access # many warnings, seems like a questionable lint - -bugprone-branch-clone + +CheckOptions: + bugprone-reserved-identifier.AllowedIdentifiers: '__asan_default_options' @@ -59,7 +59,8 @@ (Run `touch .nocontribmsg` to hide this message.) ''; - officialRelease = false; + versionJson = builtins.fromJSON (builtins.readFile ./version.json); + officialRelease = versionJson.official_release; # Set to true to build the release notes for the next release. buildUnreleasedNotes = true; @@ -275,6 +276,19 @@ # System tests. tests = import ./tests/nixos { inherit lib nixpkgs nixpkgsFor; } // { + # This is x86_64-linux only, just because we have significantly + # cheaper x86_64-linux compute in CI. + # It is clangStdenv because clang's sanitizers are nicer. + asanBuild = self.packages.x86_64-linux.nix-clangStdenv.override { + sanitize = [ + "address" + "undefined" + ]; + # it is very hard to make *every* CI build use this option such + # that we don't wind up building Lix twice, so we do it here where + # we are already doing so. + werror = true; + }; # Make sure that nix-env still produces the exact same result # on a particular version of Nixpkgs. @@ -406,7 +420,7 @@ pkgs: stdenv: let nix = pkgs.callPackage ./package.nix { - inherit stdenv officialRelease versionSuffix; + inherit stdenv versionSuffix; busybox-sandbox-shell = pkgs.busybox-sandbox-shell or pkgs.default-busybox-sandbox; internalApiDocs = false; }; diff --git a/meson.build b/meson.build index 56f447501..ed50dff78 100644 --- a/meson.build +++ b/meson.build @@ -199,7 +199,11 @@ configdata = { } # Dependencies # -boehm = dependency('bdw-gc', required : get_option('gc'), version : '>=8.2.6') +gc_opt = get_option('gc').disable_if( + 'address' in get_option('b_sanitize'), + error_message: 'gc does far too many memory crimes for ASan' +) +boehm = dependency('bdw-gc', required : gc_opt, version : '>=8.2.6') configdata += { 'HAVE_BOEHMGC': boehm.found().to_int(), } @@ -482,7 +486,14 @@ if cxx.get_id() == 'clang' and get_option('b_sanitize') != '' add_project_link_arguments('-shared-libsan', language : 'cpp') endif +# Clang gets grumpy about missing libasan symbols if -shared-libasan is not +# passed when building shared libs, at least on Linux +if cxx.get_id() == 'clang' and 'address' in get_option('b_sanitize') + add_project_link_arguments('-shared-libasan', language : 'cpp') +endif + add_project_link_arguments('-pthread', language : 'cpp') + if cxx.get_linker_id() in ['ld.bfd', 'ld.gold'] add_project_link_arguments('-Wl,--no-copy-dt-needed-entries', language : 'cpp') endif @@ -497,7 +508,7 @@ endif # maintainers/buildtime_report.sh BUILD-DIR to simply work in clang builds. # # They can also be manually viewed at https://ui.perfetto.dev -if get_option('profile-build').require(meson.get_compiler('cpp').get_id() == 'clang').enabled() +if get_option('profile-build').require(cxx.get_id() == 'clang').enabled() add_project_arguments('-ftime-trace', language: 'cpp') endif diff --git a/package.nix b/package.nix index 61015bac9..1b711585d 100644 --- a/package.nix +++ b/package.nix @@ -52,16 +52,24 @@ pname ? "lix", versionSuffix ? "", - officialRelease ? false, + officialRelease ? __forDefaults.versionJson.official_release, # Set to true to build the release notes for the next release. buildUnreleasedNotes ? true, internalApiDocs ? false, + # List of Meson sanitize options. Accepts values of b_sanitize, e.g. + # "address", "undefined", "thread". + sanitize ? null, + # Turn compiler warnings into errors. + werror ? false, + # Not a real argument, just the only way to approximate let-binding some # stuff for argument defaults. __forDefaults ? { canRunInstalled = stdenv.buildPlatform.canExecute stdenv.hostPlatform; + versionJson = builtins.fromJSON (builtins.readFile ./version.json); + boehmgc-nix = boehmgc.override { enableLargeConfig = true; }; editline-lix = editline.overrideAttrs (prev: { @@ -77,8 +85,7 @@ let inherit (lib) fileset; inherit (stdenv) hostPlatform buildPlatform; - versionJson = builtins.fromJSON (builtins.readFile ./version.json); - version = versionJson.version + versionSuffix; + version = __forDefaults.versionJson.version + versionSuffix; aws-sdk-cpp-nix = aws-sdk-cpp.override { apis = [ @@ -166,6 +173,12 @@ stdenv.mkDerivation (finalAttrs: { dontBuild = false; mesonFlags = + let + sanitizeOpts = lib.optionals (sanitize != null) ( + [ "-Db_sanitize=${builtins.concatStringsSep "," sanitize}" ] + ++ lib.optional (builtins.elem "address" sanitize) "-Dgc=disabled" + ); + in lib.optionals hostPlatform.isLinux [ # You'd think meson could just find this in PATH, but busybox is in buildInputs, # which don't actually get added to PATH. And buildInputs is correct over @@ -181,8 +194,10 @@ stdenv.mkDerivation (finalAttrs: { (lib.mesonEnable "internal-api-docs" internalApiDocs) (lib.mesonBool "enable-tests" finalAttrs.finalPackage.doCheck) (lib.mesonBool "enable-docs" canRunInstalled) + (lib.mesonBool "werror" werror) ] - ++ lib.optional (hostPlatform != buildPlatform) "--cross-file=${mesonCrossFile}"; + ++ lib.optional (hostPlatform != buildPlatform) "--cross-file=${mesonCrossFile}" + ++ sanitizeOpts; # We only include CMake so that Meson can locate toml11, which only ships CMake dependency metadata. dontUseCmakeConfigure = true; @@ -367,8 +382,6 @@ stdenv.mkDerivation (finalAttrs: { pegtl ; - inherit officialRelease; - # The collection of dependency logic for this derivation is complicated enough that # it's easier to parameterize the devShell off an already called package.nix. mkDevShell = diff --git a/releng/README.md b/releng/README.md index cfacf4b8e..2aa3b959f 100644 --- a/releng/README.md +++ b/releng/README.md @@ -30,7 +30,7 @@ First, we prepare the release. `python -m releng prepare` is used for this. Then we tag the release with `python -m releng tag`: * Git HEAD is detached. -* `officialRelease = true` is set in `flake.nix`, this is committed, and a +* `"official_release": true` is set in `version.json`, this is committed, and a release is tagged. * The tag is merged back into the last branch (either `main` for new releases or `release-MAJOR` for maintenance releases) with `git merge -s ours VERSION` diff --git a/releng/create_release.xsh b/releng/create_release.xsh index 358124359..62114350b 100644 --- a/releng/create_release.xsh +++ b/releng/create_release.xsh @@ -11,7 +11,7 @@ from . import environment from .environment import RelengEnvironment from . import keys from . import docker -from .version import VERSION, RELEASE_NAME, MAJOR +from .version import VERSION, RELEASE_NAME, MAJOR, OFFICIAL_RELEASE from .gitutils import verify_are_on_tag, git_preconditions from . import release_notes @@ -39,12 +39,18 @@ def setup_creds(env: RelengEnvironment): def official_release_commit_tag(force_tag=False): - print('[+] Setting officialRelease in flake.nix and tagging') + print('[+] Setting officialRelease in version.json and tagging') prev_branch = $(git symbolic-ref --short HEAD).strip() git switch --detach - sed -i 's/officialRelease = false/officialRelease = true/' flake.nix - git add flake.nix + + # Must be done in two parts due to buffering (opening the file immediately + # would truncate it). + new_version_json = $(jq --indent 4 '.official_release = true' version.json) + with open('version.json', 'w') as fh: + fh.write(new_version_json) + git add version.json + message = f'release: {VERSION} "{RELEASE_NAME}"\n\nRelease produced with releng/create_release.xsh' git commit -m @(message) git tag @(['-f'] if force_tag else []) -a -m @(message) @(VERSION) @@ -250,15 +256,14 @@ def build_manual(eval_result): def upload_manual(env: RelengEnvironment): - stable = json.loads($(nix eval --json '.#nix.officialRelease')) - if stable: + if OFFICIAL_RELEASE: version = MAJOR else: version = 'nightly' print('[+] aws s3 sync manual') aws s3 sync @(MANUAL)/ @(env.docs_bucket)/manual/lix/@(version)/ - if stable: + if OFFICIAL_RELEASE: aws s3 sync @(MANUAL)/ @(env.docs_bucket)/manual/lix/stable/ diff --git a/releng/version.py b/releng/version.py index 47ef23504..4ad188d46 100644 --- a/releng/version.py +++ b/releng/version.py @@ -4,3 +4,4 @@ version_json = json.load(open('version.json')) VERSION = version_json['version'] MAJOR = '.'.join(VERSION.split('.')[:2]) RELEASE_NAME = version_json['release_name'] +OFFICIAL_RELEASE = version_json['official_release'] diff --git a/src/asan-options/asan-options.cc b/src/asan-options/asan-options.cc new file mode 100644 index 000000000..c4cf360af --- /dev/null +++ b/src/asan-options/asan-options.cc @@ -0,0 +1,17 @@ +/// @file This is very bothersome code that has to be included in every +/// executable to get the correct default ASan options. I am so sorry. + +extern "C" [[gnu::retain]] const char *__asan_default_options() +{ + // We leak a bunch of memory knowingly on purpose. It's not worthwhile to + // diagnose that memory being leaked for now. + // + // Instruction bytes are useful for finding the actual code that + // corresponds to an ASan report. + // + // TODO: setting log_path=asan.log or not: neither works, since you can't + // write to the fs in certain places in the testsuite, but you also cannot + // write arbitrarily to stderr in other places so the reports get eaten. + // pain 🥖 + return "halt_on_error=1:abort_on_error=1:detect_leaks=0:print_summary=1:dump_instruction_bytes=1"; +} diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index c0e7a9a2e..a925ce2d8 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -494,6 +494,14 @@ std::ostream & operator<<(std::ostream & output, PrimOp & primOp) } +Value::Value(primop_t, PrimOp & primop) + : internalType(tPrimOp) + , primOp(&primop) + , _primop_pad(0) +{ + primop.check(); +} + PrimOp * Value::primOpAppPrimOp() const { Value * left = primOpApp.left; @@ -506,7 +514,6 @@ PrimOp * Value::primOpAppPrimOp() const return left->primOp; } - void Value::mkPrimOp(PrimOp * p) { p->check(); diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index c35f88f8d..57485aa0a 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -3,6 +3,9 @@ #include <cassert> #include <climits> +#include <functional> +#include <ranges> +#include <span> #include "gc-alloc.hh" #include "symbol-table.hh" @@ -11,6 +14,7 @@ #include "source-path.hh" #include "print-options.hh" #include "checked-arithmetic.hh" +#include "concepts.hh" #include <nlohmann/json_fwd.hpp> @@ -132,6 +136,55 @@ class ExternalValueBase std::ostream & operator << (std::ostream & str, const ExternalValueBase & v); +extern ExprBlackHole eBlackHole; + +struct NewValueAs +{ + struct integer_t { }; + constexpr static integer_t integer{}; + + struct floating_t { }; + constexpr static floating_t floating{}; + + struct boolean_t { }; + constexpr static boolean_t boolean{}; + + struct string_t { }; + constexpr static string_t string{}; + + struct path_t { }; + constexpr static path_t path{}; + + struct list_t { }; + constexpr static list_t list{}; + + struct attrs_t { }; + constexpr static attrs_t attrs{}; + + struct thunk_t { }; + constexpr static thunk_t thunk{}; + + struct null_t { }; + constexpr static null_t null{}; + + struct app_t { }; + constexpr static app_t app{}; + + struct primop_t { }; + constexpr static primop_t primop{}; + + struct primOpApp_t { }; + constexpr static primOpApp_t primOpApp{}; + + struct lambda_t { }; + constexpr static lambda_t lambda{}; + + struct external_t { }; + constexpr static external_t external{}; + + struct blackhole_t { }; + constexpr static blackhole_t blackhole{}; +}; struct Value { @@ -142,6 +195,315 @@ private: public: + // Discount `using NewValueAs::*;` +#define USING_VALUETYPE(name) using name = NewValueAs::name + USING_VALUETYPE(integer_t); + USING_VALUETYPE(floating_t); + USING_VALUETYPE(boolean_t); + USING_VALUETYPE(string_t); + USING_VALUETYPE(path_t); + USING_VALUETYPE(list_t); + USING_VALUETYPE(attrs_t); + USING_VALUETYPE(thunk_t); + USING_VALUETYPE(primop_t); + USING_VALUETYPE(app_t); + USING_VALUETYPE(null_t); + USING_VALUETYPE(primOpApp_t); + USING_VALUETYPE(lambda_t); + USING_VALUETYPE(external_t); + USING_VALUETYPE(blackhole_t); +#undef USING_VALUETYPE + + /// Default constructor which is still used in the codebase but should not + /// be used in new code. Zero initializes its members. + [[deprecated]] Value() + : internalType(static_cast<InternalType>(0)) + , _empty{ 0, 0 } + { } + + /// Constructs a nix language value of type "int", with the integral value + /// of @ref i. + Value(integer_t, NixInt i) + : internalType(tInt) + , _empty{ 0, 0 } + { + // the NixInt ctor here is is special because NixInt has a ctor too, so + // we're not allowed to have it as an anonymous aggreagte member. we do + // however still have the option to clear the data members using _empty + // and leaving the second word of data cleared by setting only integer. + integer = i; + } + + /// Constructs a nix language value of type "float", with the floating + /// point value of @ref f. + Value(floating_t, NixFloat f) + : internalType(tFloat) + , fpoint(f) + , _float_pad(0) + { } + + /// Constructs a nix language value of type "bool", with the boolean + /// value of @ref b. + Value(boolean_t, bool b) + : internalType(tBool) + , boolean(b) + , _bool_pad(0) + { } + + /// Constructs a nix language value of type "string", with the value of the + /// C-string pointed to by @ref strPtr, and optionally with an array of + /// string context pointed to by @ref contextPtr. + /// + /// Neither the C-string nor the context array are copied; this constructor + /// assumes suitable memory has already been allocated (with the GC if + /// enabled), and string and context data copied into that memory. + Value(string_t, char const * strPtr, char const ** contextPtr = nullptr) + : internalType(tString) + , string({ .s = strPtr, .context = contextPtr }) + { } + + /// Constructx a nix language value of type "string", with a copy of the + /// string data viewed by @ref copyFrom. + /// + /// The string data *is* copied from @ref copyFrom, and this constructor + /// performs a dynamic (GC) allocation to do so. + Value(string_t, std::string_view copyFrom, NixStringContext const & context = {}) + : internalType(tString) + , string({ .s = gcCopyStringIfNeeded(copyFrom), .context = nullptr }) + { + if (context.empty()) { + // It stays nullptr. + return; + } + + // Copy the context. + this->string.context = gcAllocType<char const *>(context.size() + 1); + + size_t n = 0; + for (NixStringContextElem const & contextElem : context) { + this->string.context[n] = gcCopyStringIfNeeded(contextElem.to_string()); + n += 1; + } + + // Terminator sentinel. + this->string.context[n] = nullptr; + } + + /// Constructx a nix language value of type "string", with the value of the + /// C-string pointed to by @ref strPtr, and optionally with a set of string + /// context @ref context. + /// + /// The C-string is not copied; this constructor assumes suitable memory + /// has already been allocated (with the GC if enabled), and string data + /// has been copied into that memory. The context data *is* copied from + /// @ref context, and this constructor performs a dynamic (GC) allocation + /// to do so. + Value(string_t, char const * strPtr, NixStringContext const & context) + : internalType(tString) + , string({ .s = strPtr, .context = nullptr }) + { + if (context.empty()) { + // It stays nullptr + return; + } + + // Copy the context. + this->string.context = gcAllocType<char const *>(context.size() + 1); + + size_t n = 0; + for (NixStringContextElem const & contextElem : context) { + this->string.context[n] = gcCopyStringIfNeeded(contextElem.to_string()); + n += 1; + } + + // Terminator sentinel. + this->string.context[n] = nullptr; + } + + /// Constructs a nix language value of type "path", with the value of the + /// C-string pointed to by @ref strPtr. + /// + /// The C-string is not copied; this constructor assumes suitable memory + /// has already been allocated (with the GC if enabled), and string data + /// has been copied into that memory. + Value(path_t, char const * strPtr) + : internalType(tPath) + , _path(strPtr) + , _path_pad(0) + { } + + /// Constructs a nix language value of type "path", with the path + /// @ref path. + /// + /// The data from @ref path *is* copied, and this constructor performs a + /// dynamic (GC) allocation to do so. + Value(path_t, SourcePath const & path) + : internalType(tPath) + , _path(gcCopyStringIfNeeded(path.path.abs())) + , _path_pad(0) + { } + + /// Constructs a nix language value of type "list", with element array + /// @ref items. + /// + /// Generally, the data in @ref items is neither deep copied nor shallow + /// copied. This construct assumes the std::span @ref items is a region of + /// memory that has already been allocated (with the GC if enabled), and + /// an array of valid Value pointers has been copied into that memory. + /// + /// Howver, as an implementation detail, if @ref items is only 2 items or + /// smaller, the list is stored inline, and the Value pointers in + /// @ref items are shallow copied into this structure, without dynamically + /// allocating memory. + Value(list_t, std::span<Value *> items) + { + if (items.size() == 1) { + this->internalType = tList1; + this->smallList[0] = items[0]; + this->smallList[1] = nullptr; + } else if (items.size() == 2) { + this->internalType = tList2; + this->smallList[0] = items[0]; + this->smallList[1] = items[1]; + } else { + this->internalType = tListN; + this->bigList.size = items.size(); + this->bigList.elems = items.data(); + } + } + + /// Constructs a nix language value of type "list", with an element array + /// initialized by applying @ref transformer to each element in @ref items. + /// + /// This allows "in-place" construction of a nix list when some logic is + /// needed to get each Value pointer. This constructor dynamically (GC) + /// allocates memory for the size of @ref items, and the Value pointers + /// returned by @ref transformer are shallow copied into it. + template< + std::ranges::sized_range SizedIterableT, + InvocableR<Value *, typename SizedIterableT::value_type const &> TransformerT + > + Value(list_t, SizedIterableT & items, TransformerT const & transformer) + { + if (items.size() == 1) { + this->internalType = tList1; + this->smallList[0] = transformer(*items.begin()); + this->smallList[1] = nullptr; + } else if (items.size() == 2) { + this->internalType = tList2; + auto it = items.begin(); + this->smallList[0] = transformer(*it); + it++; + this->smallList[1] = transformer(*it); + } else { + this->internalType = tListN; + this->bigList.size = items.size(); + this->bigList.elems = gcAllocType<Value *>(items.size()); + auto it = items.begin(); + for (size_t i = 0; i < items.size(); i++, it++) { + this->bigList.elems[i] = transformer(*it); + } + } + } + + /// Constructs a nix language value of the singleton type "null". + Value(null_t) + : internalType(tNull) + , _empty{0, 0} + { } + + /// Constructs a nix language value of type "set", with the attribute + /// bindings pointed to by @ref bindings. + /// + /// The bindings are not not copied; this constructor assumes @ref bindings + /// has already been suitably allocated by something like nix::buildBindings. + Value(attrs_t, Bindings * bindings) + : internalType(tAttrs) + , attrs(bindings) + , _attrs_pad(0) + { } + + /// Constructs a nix language lazy delayed computation, or "thunk". + /// + /// The thunk stores the environment it will be computed in @ref env, and + /// the expression that will need to be evaluated @ref expr. + Value(thunk_t, Env & env, Expr & expr) + : internalType(tThunk) + , thunk({ .env = &env, .expr = &expr }) + { } + + /// Constructs a nix language value of type "lambda", which represents + /// a builtin, primitive operation ("primop"), from the primop + /// implemented by @ref primop. + Value(primop_t, PrimOp & primop); + + /// Constructs a nix language value of type "lambda", which represents a + /// partially applied primop. + Value(primOpApp_t, Value & lhs, Value & rhs) + : internalType(tPrimOpApp) + , primOpApp({ .left = &lhs, .right = &rhs }) + { } + + /// Constructs a nix language value of type "lambda", which represents a + /// lazy partial application of another lambda. + Value(app_t, Value & lhs, Value & rhs) + : internalType(tApp) + , app({ .left = &lhs, .right = &rhs }) + { } + + /// Constructs a nix language value of type "external", which is only used + /// by plugins. Do any existing plugins even use this mechanism? + Value(external_t, ExternalValueBase & external) + : internalType(tExternal) + , external(&external) + , _external_pad(0) + { } + + /// Constructs a nix language value of type "lambda", which represents a + /// run of the mill lambda defined in nix code. + /// + /// This takes the environment the lambda is closed over @ref env, and + /// the lambda expression itself @ref lambda, which will not be evaluated + /// until it is applied. + Value(lambda_t, Env & env, ExprLambda & lambda) + : internalType(tLambda) + , lambda({ .env = &env, .fun = &lambda }) + { } + + /// Constructs an evil thunk, whose evaluation represents infinite recursion. + explicit Value(blackhole_t) + : internalType(tThunk) + , thunk({ .env = nullptr, .expr = reinterpret_cast<Expr *>(&eBlackHole) }) + { } + + Value(Value const & rhs) = default; + + /// Move constructor. Does the same thing as the copy constructor, but + /// also zeroes out the other Value. + Value(Value && rhs) + : internalType(rhs.internalType) + , _empty{ 0, 0 } + { + *this = std::move(rhs); + } + + Value & operator=(Value const & rhs) = default; + + /// Move assignment operator. + /// Does the same thing as the copy assignment operator, but also zeroes out + /// the rhs. + inline Value & operator=(Value && rhs) + { + *this = static_cast<const Value &>(rhs); + if (this != &rhs) { + // Kill `rhs`, because non-destructive move lol. + rhs.internalType = static_cast<InternalType>(0); + rhs._empty[0] = 0; + rhs._empty[1] = 0; + } + return *this; + } + void print(EvalState &state, std::ostream &str, PrintOptions options = PrintOptions {}); // Functions needed to distinguish the type @@ -160,8 +522,15 @@ public: union { + /// Dummy field, which takes up as much space as the largest union variants + /// to set the union's memory to zeroed memory. + uintptr_t _empty[2]; + NixInt integer; - bool boolean; + struct { + bool boolean; + uintptr_t _bool_pad; + }; /** * Strings in the evaluator carry a so-called `context` which @@ -190,8 +559,14 @@ public: const char * * context; // must be in sorted order } string; - const char * _path; - Bindings * attrs; + struct { + const char * _path; + uintptr_t _path_pad; + }; + struct { + Bindings * attrs; + uintptr_t _attrs_pad; + }; struct { size_t size; Value * * elems; @@ -208,12 +583,21 @@ public: Env * env; ExprLambda * fun; } lambda; - PrimOp * primOp; + struct { + PrimOp * primOp; + uintptr_t _primop_pad; + }; struct { Value * left, * right; } primOpApp; - ExternalValueBase * external; - NixFloat fpoint; + struct { + ExternalValueBase * external; + uintptr_t _external_pad; + }; + struct { + NixFloat fpoint; + uintptr_t _float_pad; + }; }; /** @@ -449,8 +833,6 @@ public: }; -extern ExprBlackHole eBlackHole; - bool Value::isBlackhole() const { return internalType == tThunk && thunk.expr == (Expr*) &eBlackHole; diff --git a/src/libutil/concepts.hh b/src/libutil/concepts.hh new file mode 100644 index 000000000..48bd1dbe1 --- /dev/null +++ b/src/libutil/concepts.hh @@ -0,0 +1,22 @@ +#pragma once +/// @file Defines C++ 20 concepts that std doesn't have. + +#include <type_traits> + +namespace nix +{ + +/// Like std::invocable<>, but also constrains the return type as well. +/// +/// Somehow, there is no std concept to do this, even though there is a type trait +/// for it. +/// +/// @tparam CallableT The type you want to constrain to be callable, and to return +/// @p ReturnT when called with @p Args as arguments. +/// +/// @tparam ReturnT The type the callable should return when called. +/// @tparam Args The arguments the callable should accept to return @p ReturnT. +template<typename CallableT, typename ReturnT, typename ...Args> +concept InvocableR = std::is_invocable_r_v<ReturnT, CallableT, Args...>; + +} diff --git a/src/libutil/meson.build b/src/libutil/meson.build index c860e7e00..01fe65207 100644 --- a/src/libutil/meson.build +++ b/src/libutil/meson.build @@ -58,6 +58,7 @@ libutil_headers = files( 'comparator.hh', 'compression.hh', 'compute-levels.hh', + 'concepts.hh', 'config-impl.hh', 'config.hh', 'current-process.hh', diff --git a/src/meson.build b/src/meson.build index 3fc5595b8..e918ae392 100644 --- a/src/meson.build +++ b/src/meson.build @@ -12,10 +12,19 @@ subdir('libmain') # libcmd depends on everything subdir('libcmd') - # The rest of the subdirectories aren't separate components, # just source files in another directory, so we process them here. +# Static library that just sets default ASan options. It needs to be included +# in every executable. +asanoptions = static_library( + 'libasanoptions', + files('asan-options/asan-options.cc'), +) +libasanoptions = declare_dependency( + link_whole: asanoptions +) + build_remote_sources = files( 'build-remote/build-remote.cc', ) diff --git a/src/nix/meson.build b/src/nix/meson.build index 22f148fcb..97387e402 100644 --- a/src/nix/meson.build +++ b/src/nix/meson.build @@ -80,6 +80,7 @@ nix = executable( profiles_md_gen, nix2_commands_sources, dependencies : [ + libasanoptions, liblixcmd, liblixutil_mstatic, liblixstore_mstatic, diff --git a/tests/functional/repl_characterization/meson.build b/tests/functional/repl_characterization/meson.build index 56410cfd2..79de9a5f5 100644 --- a/tests/functional/repl_characterization/meson.build +++ b/tests/functional/repl_characterization/meson.build @@ -7,6 +7,7 @@ repl_characterization_tester = executable( 'test-repl-characterization', repl_characterization_tester_sources, dependencies : [ + libasanoptions, liblixutil, liblixutil_test_support, sodium, diff --git a/tests/functional/test-libstoreconsumer/meson.build b/tests/functional/test-libstoreconsumer/meson.build index ad96aac12..63d0c97ac 100644 --- a/tests/functional/test-libstoreconsumer/meson.build +++ b/tests/functional/test-libstoreconsumer/meson.build @@ -2,6 +2,7 @@ libstoreconsumer_tester = executable( 'test-libstoreconsumer', 'main.cc', dependencies : [ + libasanoptions, liblixutil, liblixstore, sodium, diff --git a/tests/unit/meson.build b/tests/unit/meson.build index c449b2276..55c7566bd 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -11,6 +11,10 @@ # functions, the result would be way less readable than just a bit of copypasta. # It's only ~200 lines; better to just refactor the tests themselves which we'll want to do anyway. +default_test_env = { + 'ASAN_OPTIONS': 'detect_leaks=0:halt_on_error=1:abort_on_error=1:print_summary=1:dump_instruction_bytes=1' +} + libutil_test_support_sources = files( 'libutil-support/tests/cli-literate-parser.cc', 'libutil-support/tests/hash.cc', @@ -63,6 +67,7 @@ libutil_tester = executable( 'liblixutil-tests', libutil_tests_sources, dependencies : [ + libasanoptions, rapidcheck, gtest, boehm, @@ -78,7 +83,7 @@ test( 'libutil-unit-tests', libutil_tester, args : tests_args, - env : { + env : default_test_env + { '_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'tests/unit/libutil/data', }, suite : 'check', @@ -132,6 +137,7 @@ libstore_tester = executable( 'liblixstore-tests', libstore_tests_sources, dependencies : [ + libasanoptions, liblixstore_test_support, liblixutil_test_support, liblixstore_mstatic, @@ -147,7 +153,7 @@ test( 'libstore-unit-tests', libstore_tester, args : tests_args, - env : { + env : default_test_env + { '_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'tests/unit/libstore/data', }, suite : 'check', @@ -196,6 +202,7 @@ libexpr_tester = executable( 'liblixexpr-tests', libexpr_tests_sources, dependencies : [ + libasanoptions, liblixexpr_test_support, liblixstore_test_support, liblixstore_mstatic, @@ -214,7 +221,7 @@ test( 'libexpr-unit-tests', libexpr_tester, args : tests_args, - env : { + env : default_test_env + { '_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'tests/unit/libexpr/data', }, suite : 'check', @@ -226,6 +233,7 @@ libcmd_tester = executable( 'liblixcmd-tests', files('libcmd/args.cc'), dependencies : [ + libasanoptions, liblixcmd, liblixutil, liblixmain, @@ -241,7 +249,7 @@ test( 'libcmd-unit-tests', libcmd_tester, args : tests_args, - env : { + env : default_test_env + { # No special meaning here, it's just a file laying around that is unlikely to go anywhere # any time soon. '_NIX_TEST_UNIT_DATA': meson.project_source_root() / 'src/nix-env/buildenv.nix', @@ -272,6 +280,7 @@ test( 'libmain-unit-tests', libmain_tester, args : tests_args, + env : default_test_env, suite : 'check', protocol : 'gtest', ) diff --git a/version.json b/version.json index 48db2994f..809358e6d 100644 --- a/version.json +++ b/version.json @@ -1,4 +1,5 @@ { "version": "2.91.0-dev", + "official_release": false, "release_name": "TBA" } |