Merge pull request #9634 from 9999years/combine-abstract-pos-and-pos

Combine `AbstractPos`, `PosAdapter`, and `Pos`
This commit is contained in:
John Ericson 2024-01-11 10:47:27 -05:00 committed by GitHub
commit 113499d16f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 561 additions and 489 deletions

View file

@ -1,5 +1,6 @@
#include "editor-for.hh" #include "editor-for.hh"
#include "environment-variables.hh" #include "environment-variables.hh"
#include "source-path.hh"
namespace nix { namespace nix {

View file

@ -2,7 +2,7 @@
///@file ///@file
#include "types.hh" #include "types.hh"
#include "input-accessor.hh" #include "source-path.hh"
namespace nix { namespace nix {

View file

@ -1,5 +1,6 @@
#include "installable-value.hh" #include "installable-value.hh"
#include "eval-cache.hh" #include "eval-cache.hh"
#include "fetch-to-store.hh"
namespace nix { namespace nix {
@ -44,7 +45,7 @@ ref<InstallableValue> InstallableValue::require(ref<Installable> installable)
std::optional<DerivedPathWithInfo> InstallableValue::trySinglePathToDerivedPaths(Value & v, const PosIdx pos, std::string_view errorCtx) std::optional<DerivedPathWithInfo> InstallableValue::trySinglePathToDerivedPaths(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
if (v.type() == nPath) { if (v.type() == nPath) {
auto storePath = v.path().fetchToStore(*state->store); auto storePath = fetchToStore(*state->store, v.path());
return {{ return {{
.path = DerivedPath::Opaque { .path = DerivedPath::Opaque {
.path = std::move(storePath), .path = std::move(storePath),

View file

@ -221,7 +221,7 @@ static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positi
// prefer direct pos, but if noPos then try the expr. // prefer direct pos, but if noPos then try the expr.
auto pos = dt.pos auto pos = dt.pos
? dt.pos ? dt.pos
: static_cast<std::shared_ptr<AbstractPos>>(positions[dt.expr.getPos() ? dt.expr.getPos() : noPos]); : positions[dt.expr.getPos() ? dt.expr.getPos() : noPos];
if (pos) { if (pos) {
out << pos; out << pos;

View file

@ -19,6 +19,7 @@
#include "signals.hh" #include "signals.hh"
#include "gc-small-vector.hh" #include "gc-small-vector.hh"
#include "url.hh" #include "url.hh"
#include "fetch-to-store.hh"
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
@ -870,7 +871,7 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
? std::make_unique<DebugTraceStacker>( ? std::make_unique<DebugTraceStacker>(
*this, *this,
DebugTrace { DebugTrace {
.pos = error->info().errPos ? error->info().errPos : static_cast<std::shared_ptr<AbstractPos>>(positions[expr.getPos()]), .pos = error->info().errPos ? error->info().errPos : positions[expr.getPos()],
.expr = expr, .expr = expr,
.env = env, .env = env,
.hint = error->info().msg, .hint = error->info().msg,
@ -909,7 +910,7 @@ static std::unique_ptr<DebugTraceStacker> makeDebugTraceStacker(
EvalState & state, EvalState & state,
Expr & expr, Expr & expr,
Env & env, Env & env,
std::shared_ptr<AbstractPos> && pos, std::shared_ptr<Pos> && pos,
const char * s, const char * s,
const std::string & s2) const std::string & s2)
{ {
@ -1187,7 +1188,7 @@ void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial)
*this, *this,
*e, *e,
this->baseEnv, this->baseEnv,
e->getPos() ? static_cast<std::shared_ptr<AbstractPos>>(positions[e->getPos()]) : nullptr, e->getPos() ? std::make_shared<Pos>(positions[e->getPos()]) : nullptr,
"while evaluating the file '%1%':", resolvedPath.to_string()) "while evaluating the file '%1%':", resolvedPath.to_string())
: nullptr; : nullptr;
@ -2368,7 +2369,7 @@ StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePat
auto dstPath = i != srcToStore.end() auto dstPath = i != srcToStore.end()
? i->second ? i->second
: [&]() { : [&]() {
auto dstPath = path.fetchToStore(*store, path.baseName(), FileIngestionMethod::Recursive, nullptr, repair); auto dstPath = fetchToStore(*store, path, path.baseName(), FileIngestionMethod::Recursive, nullptr, repair);
allowPath(dstPath); allowPath(dstPath);
srcToStore.insert_or_assign(path, dstPath); srcToStore.insert_or_assign(path, dstPath);
printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(dstPath)); printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(dstPath));

View file

@ -142,7 +142,7 @@ struct RegexCache;
std::shared_ptr<RegexCache> makeRegexCache(); std::shared_ptr<RegexCache> makeRegexCache();
struct DebugTrace { struct DebugTrace {
std::shared_ptr<AbstractPos> pos; std::shared_ptr<Pos> pos;
const Expr & expr; const Expr & expr;
const Env & env; const Env & env;
hintformat hint; hintformat hint;

View file

@ -11,58 +11,6 @@ namespace nix {
ExprBlackHole eBlackHole; ExprBlackHole eBlackHole;
struct PosAdapter : AbstractPos
{
Pos::Origin origin;
PosAdapter(Pos::Origin origin)
: origin(std::move(origin))
{
}
std::optional<std::string> getSource() const override
{
return std::visit(overloaded {
[](const Pos::none_tag &) -> std::optional<std::string> {
return std::nullopt;
},
[](const Pos::Stdin & s) -> std::optional<std::string> {
// Get rid of the null terminators added by the parser.
return std::string(s.source->c_str());
},
[](const Pos::String & s) -> std::optional<std::string> {
// Get rid of the null terminators added by the parser.
return std::string(s.source->c_str());
},
[](const SourcePath & path) -> std::optional<std::string> {
try {
return path.readFile();
} catch (Error &) {
return std::nullopt;
}
}
}, origin);
}
void print(std::ostream & out) const override
{
std::visit(overloaded {
[&](const Pos::none_tag &) { out << "«none»"; },
[&](const Pos::Stdin &) { out << "«stdin»"; },
[&](const Pos::String & s) { out << "«string»"; },
[&](const SourcePath & path) { out << path; }
}, origin);
}
};
Pos::operator std::shared_ptr<AbstractPos>() const
{
auto pos = std::make_shared<PosAdapter>(origin);
pos->line = line;
pos->column = column;
return pos;
}
// FIXME: remove, because *symbols* are abstract and do not have a single // FIXME: remove, because *symbols* are abstract and do not have a single
// textual representation; see printIdentifier() // textual representation; see printIdentifier()
std::ostream & operator <<(std::ostream & str, const SymbolStr & symbol) std::ostream & operator <<(std::ostream & str, const SymbolStr & symbol)
@ -268,17 +216,6 @@ void ExprPos::show(const SymbolTable & symbols, std::ostream & str) const
} }
std::ostream & operator << (std::ostream & str, const Pos & pos)
{
if (auto pos2 = (std::shared_ptr<AbstractPos>) pos) {
str << *pos2;
} else
str << "undefined position";
return str;
}
std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath) std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath)
{ {
std::ostringstream out; std::ostringstream out;

View file

@ -8,6 +8,7 @@
#include "symbol-table.hh" #include "symbol-table.hh"
#include "error.hh" #include "error.hh"
#include "chunked-vector.hh" #include "chunked-vector.hh"
#include "position.hh"
namespace nix { namespace nix {
@ -28,27 +29,6 @@ public:
using EvalError::EvalError; using EvalError::EvalError;
}; };
/**
* Position objects.
*/
struct Pos
{
uint32_t line;
uint32_t column;
struct none_tag { };
struct Stdin { ref<std::string> source; };
struct String { ref<std::string> source; };
typedef std::variant<none_tag, Stdin, String, SourcePath> Origin;
Origin origin;
explicit operator bool() const { return line > 0; }
operator std::shared_ptr<AbstractPos>() const;
};
class PosIdx { class PosIdx {
friend class PosTable; friend class PosTable;
@ -81,7 +61,7 @@ public:
mutable uint32_t idx = std::numeric_limits<uint32_t>::max(); mutable uint32_t idx = std::numeric_limits<uint32_t>::max();
// Used for searching in PosTable::[]. // Used for searching in PosTable::[].
explicit Origin(uint32_t idx): idx(idx), origin{Pos::none_tag()} {} explicit Origin(uint32_t idx): idx(idx), origin{std::monostate()} {}
public: public:
const Pos::Origin origin; const Pos::Origin origin;
@ -132,8 +112,6 @@ public:
inline PosIdx noPos = {}; inline PosIdx noPos = {};
std::ostream & operator << (std::ostream & str, const Pos & pos);
struct Env; struct Env;
struct Value; struct Value;

View file

@ -16,6 +16,7 @@
#include "value-to-xml.hh" #include "value-to-xml.hh"
#include "primops.hh" #include "primops.hh"
#include "fs-input-accessor.hh" #include "fs-input-accessor.hh"
#include "fetch-to-store.hh"
#include <boost/container/small_vector.hpp> #include <boost/container/small_vector.hpp>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -2240,7 +2241,7 @@ static void addPath(
}); });
if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) { if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) {
auto dstPath = path.fetchToStore(*state.store, name, method, filter.get(), state.repair); auto dstPath = fetchToStore(*state.store, path, name, method, filter.get(), state.repair);
if (expectedHash && expectedStorePath != dstPath) if (expectedHash && expectedStorePath != dstPath)
state.debugThrowLastTrace(Error("store path mismatch in (possibly filtered) path added from '%s'", path)); state.debugThrowLastTrace(Error("store path mismatch in (possibly filtered) path added from '%s'", path));
state.allowAndSetStorePathString(dstPath, v); state.allowAndSetStorePathString(dstPath, v);

View file

@ -8,6 +8,7 @@
#include "symbol-table.hh" #include "symbol-table.hh"
#include "value/context.hh" #include "value/context.hh"
#include "input-accessor.hh" #include "input-accessor.hh"
#include "source-path.hh"
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
#include <gc/gc_allocator.h> #include <gc/gc_allocator.h>

View file

@ -0,0 +1,68 @@
#include "fetch-to-store.hh"
#include "fetchers.hh"
#include "cache.hh"
namespace nix {
StorePath fetchToStore(
Store & store,
const SourcePath & path,
std::string_view name,
ContentAddressMethod method,
PathFilter * filter,
RepairFlag repair)
{
// FIXME: add an optimisation for the case where the accessor is
// an FSInputAccessor pointing to a store path.
std::optional<fetchers::Attrs> cacheKey;
if (!filter && path.accessor->fingerprint) {
cacheKey = fetchers::Attrs{
{"_what", "fetchToStore"},
{"store", store.storeDir},
{"name", std::string(name)},
{"fingerprint", *path.accessor->fingerprint},
{
"method",
std::visit(overloaded {
[](const TextIngestionMethod &) {
return "text";
},
[](const FileIngestionMethod & fim) {
switch (fim) {
case FileIngestionMethod::Flat: return "flat";
case FileIngestionMethod::Recursive: return "nar";
default: assert(false);
}
},
}, method.raw),
},
{"path", path.path.abs()}
};
if (auto res = fetchers::getCache()->lookup(store, *cacheKey)) {
debug("store path cache hit for '%s'", path);
return res->second;
}
} else
debug("source path '%s' is uncacheable", path);
Activity act(*logger, lvlChatty, actUnknown, fmt("copying '%s' to the store", path));
auto filter2 = filter ? *filter : defaultPathFilter;
auto storePath =
settings.readOnlyMode
? store.computeStorePath(
name, *path.accessor, path.path, method, HashAlgorithm::SHA256, {}, filter2).first
: store.addToStore(
name, *path.accessor, path.path, method, HashAlgorithm::SHA256, {}, filter2, repair);
if (cacheKey)
fetchers::getCache()->add(store, *cacheKey, {}, storePath, true);
return storePath;
}
}

View file

@ -0,0 +1,22 @@
#pragma once
#include "source-path.hh"
#include "store-api.hh"
#include "file-system.hh"
#include "repair-flag.hh"
#include "file-content-address.hh"
namespace nix {
/**
* Copy the `path` to the Nix store.
*/
StorePath fetchToStore(
Store & store,
const SourcePath & path,
std::string_view name = "source",
ContentAddressMethod method = FileIngestionMethod::Recursive,
PathFilter * filter = nullptr,
RepairFlag repair = NoRepair);
}

View file

@ -1,6 +1,8 @@
#include "fetchers.hh" #include "fetchers.hh"
#include "store-api.hh" #include "store-api.hh"
#include "input-accessor.hh" #include "input-accessor.hh"
#include "source-path.hh"
#include "fetch-to-store.hh"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -374,7 +376,7 @@ void InputScheme::clone(const Input & input, const Path & destDir) const
std::pair<StorePath, Input> InputScheme::fetch(ref<Store> store, const Input & input) std::pair<StorePath, Input> InputScheme::fetch(ref<Store> store, const Input & input)
{ {
auto [accessor, input2] = getAccessor(store, input); auto [accessor, input2] = getAccessor(store, input);
auto storePath = SourcePath(accessor).fetchToStore(*store, input2.getName()); auto storePath = fetchToStore(*store, SourcePath(accessor), input2.getName());
return {storePath, input2}; return {storePath, input2};
} }

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "input-accessor.hh" #include "input-accessor.hh"
#include "source-path.hh"
namespace nix { namespace nix {

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
#include "input-accessor.hh" #include "input-accessor.hh"
#include "source-path.hh"
namespace nix { namespace nix {

View file

@ -1,129 +0,0 @@
#include "input-accessor.hh"
#include "store-api.hh"
#include "cache.hh"
namespace nix {
StorePath InputAccessor::fetchToStore(
Store & store,
const CanonPath & path,
std::string_view name,
ContentAddressMethod method,
PathFilter * filter,
RepairFlag repair)
{
// FIXME: add an optimisation for the case where the accessor is
// an FSInputAccessor pointing to a store path.
std::optional<fetchers::Attrs> cacheKey;
if (!filter && fingerprint) {
cacheKey = fetchers::Attrs{
{"_what", "fetchToStore"},
{"store", store.storeDir},
{"name", std::string(name)},
{"fingerprint", *fingerprint},
{
"method",
std::visit(overloaded {
[](const TextIngestionMethod &) {
return "text";
},
[](const FileIngestionMethod & fim) {
switch (fim) {
case FileIngestionMethod::Flat: return "flat";
case FileIngestionMethod::Recursive: return "nar";
default: assert(false);
}
},
}, method.raw),
},
{"path", path.abs()}
};
if (auto res = fetchers::getCache()->lookup(store, *cacheKey)) {
debug("store path cache hit for '%s'", showPath(path));
return res->second;
}
} else
debug("source path '%s' is uncacheable", showPath(path));
Activity act(*logger, lvlChatty, actUnknown, fmt("copying '%s' to the store", showPath(path)));
auto filter2 = filter ? *filter : defaultPathFilter;
auto storePath =
settings.readOnlyMode
? store.computeStorePath(
name, *this, path, method, HashAlgorithm::SHA256, {}, filter2).first
: store.addToStore(
name, *this, path, method, HashAlgorithm::SHA256, {}, filter2, repair);
if (cacheKey)
fetchers::getCache()->add(store, *cacheKey, {}, storePath, true);
return storePath;
}
std::ostream & operator << (std::ostream & str, const SourcePath & path)
{
str << path.to_string();
return str;
}
StorePath SourcePath::fetchToStore(
Store & store,
std::string_view name,
ContentAddressMethod method,
PathFilter * filter,
RepairFlag repair) const
{
return accessor->fetchToStore(store, path, name, method, filter, repair);
}
std::string_view SourcePath::baseName() const
{
return path.baseName().value_or("source");
}
SourcePath SourcePath::parent() const
{
auto p = path.parent();
assert(p);
return {accessor, std::move(*p)};
}
SourcePath SourcePath::resolveSymlinks() const
{
auto res = SourcePath(accessor);
int linksAllowed = 1024;
std::list<std::string> todo;
for (auto & c : path)
todo.push_back(std::string(c));
while (!todo.empty()) {
auto c = *todo.begin();
todo.pop_front();
if (c == "" || c == ".")
;
else if (c == "..")
res.path.pop();
else {
res.path.push(c);
if (auto st = res.maybeLstat(); st && st->type == InputAccessor::tSymlink) {
if (!linksAllowed--)
throw Error("infinite symlink recursion in path '%s'", path);
auto target = res.readLink();
res.path.pop();
if (hasPrefix(target, "/"))
res.path = CanonPath::root;
todo.splice(todo.begin(), tokenizeString<std::list<std::string>>(target, "/"));
}
}
}
return res;
}
}

View file

@ -1,174 +0,0 @@
#pragma once
///@file
#include "source-accessor.hh"
#include "ref.hh"
#include "types.hh"
#include "file-system.hh"
#include "repair-flag.hh"
#include "content-address.hh"
namespace nix {
MakeError(RestrictedPathError, Error);
struct SourcePath;
class StorePath;
class Store;
struct InputAccessor : virtual SourceAccessor, std::enable_shared_from_this<InputAccessor>
{
std::optional<std::string> fingerprint;
/**
* Return the maximum last-modified time of the files in this
* tree, if available.
*/
virtual std::optional<time_t> getLastModified()
{
return std::nullopt;
}
StorePath fetchToStore(
Store & store,
const CanonPath & path,
std::string_view name = "source",
ContentAddressMethod method = FileIngestionMethod::Recursive,
PathFilter * filter = nullptr,
RepairFlag repair = NoRepair);
};
/**
* An abstraction for accessing source files during
* evaluation. Currently, it's just a wrapper around `CanonPath` that
* accesses files in the regular filesystem, but in the future it will
* support fetching files in other ways.
*/
struct SourcePath
{
ref<InputAccessor> accessor;
CanonPath path;
SourcePath(ref<InputAccessor> accessor, CanonPath path = CanonPath::root)
: accessor(std::move(accessor))
, path(std::move(path))
{ }
std::string_view baseName() const;
/**
* Construct the parent of this `SourcePath`. Aborts if `this`
* denotes the root.
*/
SourcePath parent() const;
/**
* If this `SourcePath` denotes a regular file (not a symlink),
* return its contents; otherwise throw an error.
*/
std::string readFile() const
{ return accessor->readFile(path); }
/**
* Return whether this `SourcePath` denotes a file (of any type)
* that exists
*/
bool pathExists() const
{ return accessor->pathExists(path); }
/**
* Return stats about this `SourcePath`, or throw an exception if
* it doesn't exist.
*/
InputAccessor::Stat lstat() const
{ return accessor->lstat(path); }
/**
* Return stats about this `SourcePath`, or std::nullopt if it
* doesn't exist.
*/
std::optional<InputAccessor::Stat> maybeLstat() const
{ return accessor->maybeLstat(path); }
/**
* If this `SourcePath` denotes a directory (not a symlink),
* return its directory entries; otherwise throw an error.
*/
InputAccessor::DirEntries readDirectory() const
{ return accessor->readDirectory(path); }
/**
* If this `SourcePath` denotes a symlink, return its target;
* otherwise throw an error.
*/
std::string readLink() const
{ return accessor->readLink(path); }
/**
* Dump this `SourcePath` to `sink` as a NAR archive.
*/
void dumpPath(
Sink & sink,
PathFilter & filter = defaultPathFilter) const
{ return accessor->dumpPath(path, sink, filter); }
/**
* Copy this `SourcePath` to the Nix store.
*/
StorePath fetchToStore(
Store & store,
std::string_view name = "source",
ContentAddressMethod method = FileIngestionMethod::Recursive,
PathFilter * filter = nullptr,
RepairFlag repair = NoRepair) const;
/**
* Return the location of this path in the "real" filesystem, if
* it has a physical location.
*/
std::optional<CanonPath> getPhysicalPath() const
{ return accessor->getPhysicalPath(path); }
std::string to_string() const
{ return accessor->showPath(path); }
/**
* Append a `CanonPath` to this path.
*/
SourcePath operator + (const CanonPath & x) const
{ return {accessor, path + x}; }
/**
* Append a single component `c` to this path. `c` must not
* contain a slash. A slash is implicitly added between this path
* and `c`.
*/
SourcePath operator + (std::string_view c) const
{ return {accessor, path + c}; }
bool operator == (const SourcePath & x) const
{
return std::tie(accessor, path) == std::tie(x.accessor, x.path);
}
bool operator != (const SourcePath & x) const
{
return std::tie(accessor, path) != std::tie(x.accessor, x.path);
}
bool operator < (const SourcePath & x) const
{
return std::tie(accessor, path) < std::tie(x.accessor, x.path);
}
/**
* Resolve any symlinks in this `SourcePath` (including its
* parents). The result is a `SourcePath` in which no element is a
* symlink.
*/
SourcePath resolveSymlinks() const;
};
std::ostream & operator << (std::ostream & str, const SourcePath & path);
}

View file

@ -1,5 +1,6 @@
#include "memory-input-accessor.hh" #include "memory-input-accessor.hh"
#include "memory-source-accessor.hh" #include "memory-source-accessor.hh"
#include "source-path.hh"
namespace nix { namespace nix {

View file

@ -1,4 +1,5 @@
#include "input-accessor.hh" #include "input-accessor.hh"
#include "source-path.hh"
namespace nix { namespace nix {

View file

@ -13,6 +13,7 @@
#include "path-info.hh" #include "path-info.hh"
#include "repair-flag.hh" #include "repair-flag.hh"
#include "store-dir-config.hh" #include "store-dir-config.hh"
#include "source-path.hh"
#include <nlohmann/json_fwd.hpp> #include <nlohmann/json_fwd.hpp>
#include <atomic> #include <atomic>

View file

@ -2,6 +2,7 @@
#include "environment-variables.hh" #include "environment-variables.hh"
#include "signals.hh" #include "signals.hh"
#include "terminal.hh" #include "terminal.hh"
#include "position.hh"
#include <iostream> #include <iostream>
#include <optional> #include <optional>
@ -10,7 +11,7 @@
namespace nix { namespace nix {
void BaseError::addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint, bool frame) void BaseError::addTrace(std::shared_ptr<Pos> && e, hintformat hint, bool frame)
{ {
err.traces.push_front(Trace { .pos = std::move(e), .hint = hint, .frame = frame }); err.traces.push_front(Trace { .pos = std::move(e), .hint = hint, .frame = frame });
} }
@ -41,15 +42,6 @@ std::ostream & operator <<(std::ostream & os, const hintformat & hf)
return os << hf.str(); return os << hf.str();
} }
std::ostream & operator <<(std::ostream & str, const AbstractPos & pos)
{
pos.print(str);
str << ":" << pos.line;
if (pos.column > 0)
str << ":" << pos.column;
return str;
}
/** /**
* An arbitrarily defined value comparison for the purpose of using traces in the key of a sorted container. * An arbitrarily defined value comparison for the purpose of using traces in the key of a sorted container.
*/ */
@ -76,49 +68,10 @@ inline bool operator> (const Trace& lhs, const Trace& rhs) { return rhs < lhs; }
inline bool operator<=(const Trace& lhs, const Trace& rhs) { return !(lhs > rhs); } inline bool operator<=(const Trace& lhs, const Trace& rhs) { return !(lhs > rhs); }
inline bool operator>=(const Trace& lhs, const Trace& rhs) { return !(lhs < rhs); } inline bool operator>=(const Trace& lhs, const Trace& rhs) { return !(lhs < rhs); }
std::optional<LinesOfCode> AbstractPos::getCodeLines() const
{
if (line == 0)
return std::nullopt;
if (auto source = getSource()) {
std::istringstream iss(*source);
// count the newlines.
int count = 0;
std::string curLine;
int pl = line - 1;
LinesOfCode loc;
do {
std::getline(iss, curLine);
++count;
if (count < pl)
;
else if (count == pl) {
loc.prevLineOfCode = curLine;
} else if (count == pl + 1) {
loc.errLineOfCode = curLine;
} else if (count == pl + 2) {
loc.nextLineOfCode = curLine;
break;
}
if (!iss.good())
break;
} while (true);
return loc;
}
return std::nullopt;
}
// print lines of code to the ostream, indicating the error column. // print lines of code to the ostream, indicating the error column.
void printCodeLines(std::ostream & out, void printCodeLines(std::ostream & out,
const std::string & prefix, const std::string & prefix,
const AbstractPos & errPos, const Pos & errPos,
const LinesOfCode & loc) const LinesOfCode & loc)
{ {
// previous line of code. // previous line of code.
@ -196,7 +149,7 @@ static bool printUnknownLocations = getEnv("_NIX_EVAL_SHOW_UNKNOWN_LOCATIONS").h
* *
* @return true if a position was printed. * @return true if a position was printed.
*/ */
static bool printPosMaybe(std::ostream & oss, std::string_view indent, const std::shared_ptr<AbstractPos> & pos) { static bool printPosMaybe(std::ostream & oss, std::string_view indent, const std::shared_ptr<Pos> & pos) {
bool hasPos = pos && *pos; bool hasPos = pos && *pos;
if (hasPos) { if (hasPos) {
oss << indent << ANSI_BLUE << "at " ANSI_WARNING << *pos << ANSI_NORMAL << ":"; oss << indent << ANSI_BLUE << "at " ANSI_WARNING << *pos << ANSI_NORMAL << ":";

View file

@ -63,45 +63,15 @@ struct LinesOfCode {
std::optional<std::string> nextLineOfCode; std::optional<std::string> nextLineOfCode;
}; };
/** struct Pos;
* An abstract type that represents a location in a source file.
*/
struct AbstractPos
{
uint32_t line = 0;
uint32_t column = 0;
/**
* An AbstractPos may be a "null object", representing an unknown position.
*
* Return true if this position is known.
*/
inline operator bool() const { return line != 0; };
/**
* Return the contents of the source file.
*/
virtual std::optional<std::string> getSource() const
{ return std::nullopt; };
virtual void print(std::ostream & out) const = 0;
std::optional<LinesOfCode> getCodeLines() const;
virtual ~AbstractPos() = default;
inline auto operator<=>(const AbstractPos& rhs) const = default;
};
std::ostream & operator << (std::ostream & str, const AbstractPos & pos);
void printCodeLines(std::ostream & out, void printCodeLines(std::ostream & out,
const std::string & prefix, const std::string & prefix,
const AbstractPos & errPos, const Pos & errPos,
const LinesOfCode & loc); const LinesOfCode & loc);
struct Trace { struct Trace {
std::shared_ptr<AbstractPos> pos; std::shared_ptr<Pos> pos;
hintformat hint; hintformat hint;
bool frame; bool frame;
}; };
@ -114,7 +84,7 @@ inline bool operator>=(const Trace& lhs, const Trace& rhs);
struct ErrorInfo { struct ErrorInfo {
Verbosity level; Verbosity level;
hintformat msg; hintformat msg;
std::shared_ptr<AbstractPos> errPos; std::shared_ptr<Pos> errPos;
std::list<Trace> traces; std::list<Trace> traces;
Suggestions suggestions; Suggestions suggestions;
@ -185,12 +155,12 @@ public:
} }
template<typename... Args> template<typename... Args>
void addTrace(std::shared_ptr<AbstractPos> && e, std::string_view fs, const Args & ... args) void addTrace(std::shared_ptr<Pos> && e, std::string_view fs, const Args & ... args)
{ {
addTrace(std::move(e), hintfmt(std::string(fs), args...)); addTrace(std::move(e), hintfmt(std::string(fs), args...));
} }
void addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint, bool frame = false); void addTrace(std::shared_ptr<Pos> && e, hintformat hint, bool frame = false);
bool hasTrace() const { return !err.traces.empty(); } bool hasTrace() const { return !err.traces.empty(); }

View file

@ -0,0 +1,27 @@
#pragma once
///@file
#include "source-accessor.hh"
#include "ref.hh"
#include "repair-flag.hh"
namespace nix {
MakeError(RestrictedPathError, Error);
struct InputAccessor : virtual SourceAccessor, std::enable_shared_from_this<InputAccessor>
{
std::optional<std::string> fingerprint;
/**
* Return the maximum last-modified time of the files in this
* tree, if available.
*/
virtual std::optional<time_t> getLastModified()
{
return std::nullopt;
}
};
}

View file

@ -4,6 +4,8 @@
#include "terminal.hh" #include "terminal.hh"
#include "util.hh" #include "util.hh"
#include "config.hh" #include "config.hh"
#include "source-path.hh"
#include "position.hh"
#include <atomic> #include <atomic>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@ -136,13 +138,13 @@ Activity::Activity(Logger & logger, Verbosity lvl, ActivityType type,
logger.startActivity(id, lvl, type, s, fields, parent); logger.startActivity(id, lvl, type, s, fields, parent);
} }
void to_json(nlohmann::json & json, std::shared_ptr<AbstractPos> pos) void to_json(nlohmann::json & json, std::shared_ptr<Pos> pos)
{ {
if (pos) { if (pos) {
json["line"] = pos->line; json["line"] = pos->line;
json["column"] = pos->column; json["column"] = pos->column;
std::ostringstream str; std::ostringstream str;
pos->print(str); pos->print(str, true);
json["file"] = str.str(); json["file"] = str.str();
} else { } else {
json["line"] = nullptr; json["line"] = nullptr;

112
src/libutil/position.cc Normal file
View file

@ -0,0 +1,112 @@
#include "position.hh"
namespace nix {
Pos::Pos(const Pos * other)
{
if (!other) {
return;
}
line = other->line;
column = other->column;
origin = std::move(other->origin);
}
Pos::operator std::shared_ptr<Pos>() const
{
return std::make_shared<Pos>(&*this);
}
bool Pos::operator<(const Pos &rhs) const
{
return std::forward_as_tuple(line, column, origin)
< std::forward_as_tuple(rhs.line, rhs.column, rhs.origin);
}
std::optional<LinesOfCode> Pos::getCodeLines() const
{
if (line == 0)
return std::nullopt;
if (auto source = getSource()) {
std::istringstream iss(*source);
// count the newlines.
int count = 0;
std::string curLine;
int pl = line - 1;
LinesOfCode loc;
do {
std::getline(iss, curLine);
++count;
if (count < pl)
;
else if (count == pl) {
loc.prevLineOfCode = curLine;
} else if (count == pl + 1) {
loc.errLineOfCode = curLine;
} else if (count == pl + 2) {
loc.nextLineOfCode = curLine;
break;
}
if (!iss.good())
break;
} while (true);
return loc;
}
return std::nullopt;
}
std::optional<std::string> Pos::getSource() const
{
return std::visit(overloaded {
[](const std::monostate &) -> std::optional<std::string> {
return std::nullopt;
},
[](const Pos::Stdin & s) -> std::optional<std::string> {
// Get rid of the null terminators added by the parser.
return std::string(s.source->c_str());
},
[](const Pos::String & s) -> std::optional<std::string> {
// Get rid of the null terminators added by the parser.
return std::string(s.source->c_str());
},
[](const SourcePath & path) -> std::optional<std::string> {
try {
return path.readFile();
} catch (Error &) {
return std::nullopt;
}
}
}, origin);
}
void Pos::print(std::ostream & out, bool showOrigin) const
{
if (showOrigin) {
std::visit(overloaded {
[&](const std::monostate &) { out << "«none»"; },
[&](const Pos::Stdin &) { out << "«stdin»"; },
[&](const Pos::String & s) { out << "«string»"; },
[&](const SourcePath & path) { out << path; }
}, origin);
out << ":";
}
out << line;
if (column > 0)
out << ":" << column;
}
std::ostream & operator<<(std::ostream & str, const Pos & pos)
{
pos.print(str, true);
return str;
}
}

74
src/libutil/position.hh Normal file
View file

@ -0,0 +1,74 @@
#pragma once
/**
* @file
*
* @brief Pos and AbstractPos
*/
#include <cstdint>
#include <string>
#include "source-path.hh"
namespace nix {
/**
* A position and an origin for that position (like a source file).
*/
struct Pos
{
uint32_t line = 0;
uint32_t column = 0;
struct Stdin {
ref<std::string> source;
bool operator==(const Stdin & rhs) const
{ return *source == *rhs.source; }
bool operator!=(const Stdin & rhs) const
{ return *source != *rhs.source; }
bool operator<(const Stdin & rhs) const
{ return *source < *rhs.source; }
};
struct String {
ref<std::string> source;
bool operator==(const String & rhs) const
{ return *source == *rhs.source; }
bool operator!=(const String & rhs) const
{ return *source != *rhs.source; }
bool operator<(const String & rhs) const
{ return *source < *rhs.source; }
};
typedef std::variant<std::monostate, Stdin, String, SourcePath> Origin;
Origin origin = std::monostate();
Pos() { }
Pos(uint32_t line, uint32_t column, Origin origin)
: line(line), column(column), origin(origin) { }
Pos(Pos & other) = default;
Pos(const Pos & other) = default;
Pos(Pos && other) = default;
Pos(const Pos * other);
explicit operator bool() const { return line > 0; }
operator std::shared_ptr<Pos>() const;
/**
* Return the contents of the source file.
*/
std::optional<std::string> getSource() const;
void print(std::ostream & out, bool showOrigin) const;
std::optional<LinesOfCode> getCodeLines() const;
bool operator==(const Pos & rhs) const = default;
bool operator!=(const Pos & rhs) const = default;
bool operator<(const Pos & rhs) const;
};
std::ostream & operator<<(std::ostream & str, const Pos & pos);
}

View file

@ -1,6 +1,7 @@
#pragma once #pragma once
///@file ///@file
#include <compare>
#include <memory> #include <memory>
#include <exception> #include <exception>
#include <stdexcept> #include <stdexcept>

105
src/libutil/source-path.cc Normal file
View file

@ -0,0 +1,105 @@
#include "source-path.hh"
namespace nix {
std::string_view SourcePath::baseName() const
{ return path.baseName().value_or("source"); }
SourcePath SourcePath::parent() const
{
auto p = path.parent();
assert(p);
return {accessor, std::move(*p)};
}
std::string SourcePath::readFile() const
{ return accessor->readFile(path); }
bool SourcePath::pathExists() const
{ return accessor->pathExists(path); }
InputAccessor::Stat SourcePath::lstat() const
{ return accessor->lstat(path); }
std::optional<InputAccessor::Stat> SourcePath::maybeLstat() const
{ return accessor->maybeLstat(path); }
InputAccessor::DirEntries SourcePath::readDirectory() const
{ return accessor->readDirectory(path); }
std::string SourcePath::readLink() const
{ return accessor->readLink(path); }
void SourcePath::dumpPath(
Sink & sink,
PathFilter & filter) const
{ return accessor->dumpPath(path, sink, filter); }
std::optional<CanonPath> SourcePath::getPhysicalPath() const
{ return accessor->getPhysicalPath(path); }
std::string SourcePath::to_string() const
{ return accessor->showPath(path); }
SourcePath SourcePath::operator+(const CanonPath & x) const
{ return {accessor, path + x}; }
SourcePath SourcePath::operator+(std::string_view c) const
{ return {accessor, path + c}; }
bool SourcePath::operator==(const SourcePath & x) const
{
return std::tie(*accessor, path) == std::tie(*x.accessor, x.path);
}
bool SourcePath::operator!=(const SourcePath & x) const
{
return std::tie(*accessor, path) != std::tie(*x.accessor, x.path);
}
bool SourcePath::operator<(const SourcePath & x) const
{
return std::tie(*accessor, path) < std::tie(*x.accessor, x.path);
}
SourcePath SourcePath::resolveSymlinks() const
{
auto res = SourcePath(accessor);
int linksAllowed = 1024;
std::list<std::string> todo;
for (auto & c : path)
todo.push_back(std::string(c));
while (!todo.empty()) {
auto c = *todo.begin();
todo.pop_front();
if (c == "" || c == ".")
;
else if (c == "..")
res.path.pop();
else {
res.path.push(c);
if (auto st = res.maybeLstat(); st && st->type == InputAccessor::tSymlink) {
if (!linksAllowed--)
throw Error("infinite symlink recursion in path '%s'", path);
auto target = res.readLink();
res.path.pop();
if (hasPrefix(target, "/"))
res.path = CanonPath::root;
todo.splice(todo.begin(), tokenizeString<std::list<std::string>>(target, "/"));
}
}
}
return res;
}
std::ostream & operator<<(std::ostream & str, const SourcePath & path)
{
str << path.to_string();
return str;
}
}

114
src/libutil/source-path.hh Normal file
View file

@ -0,0 +1,114 @@
#pragma once
/**
* @file
*
* @brief SourcePath
*/
#include "ref.hh"
#include "canon-path.hh"
#include "input-accessor.hh"
namespace nix {
/**
* An abstraction for accessing source files during
* evaluation. Currently, it's just a wrapper around `CanonPath` that
* accesses files in the regular filesystem, but in the future it will
* support fetching files in other ways.
*/
struct SourcePath
{
ref<InputAccessor> accessor;
CanonPath path;
SourcePath(ref<InputAccessor> accessor, CanonPath path = CanonPath::root)
: accessor(std::move(accessor))
, path(std::move(path))
{ }
std::string_view baseName() const;
/**
* Construct the parent of this `SourcePath`. Aborts if `this`
* denotes the root.
*/
SourcePath parent() const;
/**
* If this `SourcePath` denotes a regular file (not a symlink),
* return its contents; otherwise throw an error.
*/
std::string readFile() const;
/**
* Return whether this `SourcePath` denotes a file (of any type)
* that exists
*/
bool pathExists() const;
/**
* Return stats about this `SourcePath`, or throw an exception if
* it doesn't exist.
*/
InputAccessor::Stat lstat() const;
/**
* Return stats about this `SourcePath`, or std::nullopt if it
* doesn't exist.
*/
std::optional<InputAccessor::Stat> maybeLstat() const;
/**
* If this `SourcePath` denotes a directory (not a symlink),
* return its directory entries; otherwise throw an error.
*/
InputAccessor::DirEntries readDirectory() const;
/**
* If this `SourcePath` denotes a symlink, return its target;
* otherwise throw an error.
*/
std::string readLink() const;
/**
* Dump this `SourcePath` to `sink` as a NAR archive.
*/
void dumpPath(
Sink & sink,
PathFilter & filter = defaultPathFilter) const;
/**
* Return the location of this path in the "real" filesystem, if
* it has a physical location.
*/
std::optional<CanonPath> getPhysicalPath() const;
std::string to_string() const;
/**
* Append a `CanonPath` to this path.
*/
SourcePath operator + (const CanonPath & x) const;
/**
* Append a single component `c` to this path. `c` must not
* contain a slash. A slash is implicitly added between this path
* and `c`.
*/
SourcePath operator+(std::string_view c) const;
bool operator==(const SourcePath & x) const;
bool operator!=(const SourcePath & x) const;
bool operator<(const SourcePath & x) const;
/**
* Resolve any symlinks in this `SourcePath` (including its
* parents). The result is a `SourcePath` in which no element is a
* symlink.
*/
SourcePath resolveSymlinks() const;
};
std::ostream & operator << (std::ostream & str, const SourcePath & path);
}