Skip to content

add_file_node re-roots a node whose parent it cannot resolve instead of refusing it #492

Description

@typeless

Symptom

add_file_node accepts a parent_dir it cannot resolve. It files the node under the source root instead of refusing it, while the parent's own index still decides which dir_children bucket the name goes into.

Mechanism

In add_file_node (src/graph/dag.cpp), the path comes from the parent node's path_id. When get_file_node(parent_dir) returns null, it falls back to the source root:

auto parent_path = PathId::SourceRoot;
if (node.parent_dir != 0) {
    auto const* parent = get_file_node(std::as_const(graph), node.parent_dir);
    if (parent) {
        parent_path = parent->path_id;
    }
}

get_file_node returns null for a command-, condition- or phi-tagged id, an index past the end, or an id whose slot holds a different node. In each of those cases:

  1. The node is registered in path_to_node at <source root>/<name>, a path the caller never asked for.
  2. The dir_children registration is keyed on node_id::index(parent_dir), with the tag stripped, so it goes into another node's bucket or past the end.
  3. graph.dir_children is resized only to the new node's index, and Vec::operator[] is unchecked (include/pup/core/vec.hpp). So a parent_dir whose index exceeds the new node's writes out of bounds. I read this from the source and did not exercise it.

Observed with a probe linked against build/libputup.a from the PR #490 branch. The probe adds root foo.c, then foo.c under unminted parent 999:

P2a root foo.c: ok id=2 next_file_id=3
P2b foo.c under invalid parent 999: ERR code=21 msg=[Unable to create 'foo.c' because a file already occupies that path] next_file_id=3

With #490 the call fails, but the message names a path the caller did not request. On main without #490, the same call silently repoints path_to_node["foo.c"] at the new node.

Reachability

None from production that I could find. The five add_file_node call sites take their parent either from ensure_file_node's result or from NodeId{0}. The fallback is a latent hazard with no known trigger, so it's filed separately from #487 instead of being folded into its fix.

Decision to make

Whether a non-zero parent_dir that does not resolve should be an error (ErrorCode::InvalidNodeId, which add_edge already uses for an unresolvable endpoint) or an assertion. Either removes the silent re-rooting and the out-of-bounds index together.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions