commit c9baaf8f7d59e4bed9f7dc7d280dc692945c5d86
parent 89191859b440d20f149e51762e4f888b69116955
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 11 May 2026 10:59:19 -0700
pp: search includer's directory first for quoted #include
Per C §6.10.2, `#include "x.h"` resolves relative to the directory of
the file containing the directive. Previously the quoted form tried the
path verbatim (CWD-relative) and then walked -I dirs, which only worked
when the driver happened to be invoked from the source's directory.
find_and_open_include now derives the includer's dir from loc.file_id
(falling back to CWD for synthetic sources like <command-line>) and
tries it ahead of the -I search. Absolute paths bypass both and open
verbatim. Bracket form is unchanged.
Diffstat:
| M | src/pp/pp.c | | | 77 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------- |
1 file changed, 68 insertions(+), 9 deletions(-)
diff --git a/src/pp/pp.c b/src/pp/pp.c
@@ -1241,23 +1241,82 @@ static int try_open_include(Pp* pp, const char* path, const u8** data_out,
return 1;
}
-/* Search for a header. Quoted form ("...") tries the path verbatim first
- * (covering relative-to-CWD which is dir-of-current for the tests),
- * then walks user and system include dirs. Bracket form (<...>) skips the
- * verbatim attempt. */
+/* Return the includer's directory for resolving a quoted include, or "."
+ * for in-memory/builtin sources (where CWD is the natural fallback, like
+ * gcc treats stdin). `dir_out` must point to a buffer of size >= cap. */
+static int includer_dir(Pp* pp, SrcLoc loc, char* dir_out, size_t cap) {
+ const SourceFile* sf = source_file(pp->c->sources, loc.file_id);
+ const char* p = NULL;
+ size_t plen = 0;
+ const char* slash;
+ size_t dlen;
+ if (sf && sf->name) p = pool_str(pp->c->global, sf->name, &plen);
+ if (!p || plen == 0 || p[0] == '<') {
+ if (cap < 2) return 0;
+ dir_out[0] = '.';
+ dir_out[1] = 0;
+ return 1;
+ }
+ slash = NULL;
+ {
+ size_t i;
+ for (i = plen; i > 0; --i) {
+ if (p[i - 1] == '/') {
+ slash = p + i - 1;
+ break;
+ }
+ }
+ }
+ if (!slash) {
+ if (cap < 2) return 0;
+ dir_out[0] = '.';
+ dir_out[1] = 0;
+ return 1;
+ }
+ dlen = (size_t)(slash - p);
+ if (dlen == 0) dlen = 1; /* path was "/x" — dir is "/" */
+ if (dlen + 1 > cap) return 0;
+ memcpy(dir_out, p, dlen);
+ dir_out[dlen] = 0;
+ return 1;
+}
+
+/* Search for a header. Absolute paths are opened verbatim. Quoted form
+ * ("...") additionally searches the directory of the file containing the
+ * #include first (per C §6.10.2); bracket form (<...>) skips that step.
+ * Both forms then walk the configured -I / -isystem dirs in order. */
static int find_and_open_include(Pp* pp, const char* path, int system,
- const u8** data, size_t* size, char* resolved,
- size_t resolved_cap) {
+ SrcLoc loc, const u8** data, size_t* size,
+ char* resolved, size_t resolved_cap) {
char buf[4096];
u32 i;
size_t plen = strlen(path);
- if (!system) {
+ if (plen > 0 && path[0] == '/') {
if (try_open_include(pp, path, data, size)) {
if (plen + 1 > resolved_cap) return 0;
memcpy(resolved, path, plen + 1);
return 1;
}
+ return 0;
+ }
+
+ if (!system) {
+ char dir[4096];
+ if (includer_dir(pp, loc, dir, sizeof(dir))) {
+ size_t dlen = strlen(dir);
+ if (dlen + 1 + plen + 1 <= sizeof(buf)) {
+ memcpy(buf, dir, dlen);
+ buf[dlen] = '/';
+ memcpy(buf + dlen + 1, path, plen);
+ buf[dlen + 1 + plen] = 0;
+ if (try_open_include(pp, buf, data, size)) {
+ if (dlen + 1 + plen + 1 > resolved_cap) return 0;
+ memcpy(resolved, buf, dlen + 1 + plen + 1);
+ return 1;
+ }
+ }
+ }
}
for (i = 0; i < pp->ninc_dirs; ++i) {
const char* d = pp->inc_dirs[i].path;
@@ -1365,7 +1424,7 @@ static void do_include(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
parse_include_path(pp, line, n, loc, path, sizeof(path), &system_form);
- if (!find_and_open_include(pp, path, system_form, &data, &size, resolved,
+ if (!find_and_open_include(pp, path, system_form, loc, &data, &size, resolved,
sizeof(resolved))) {
compiler_panic(pp->c, loc, "#include: file not found: %s", path);
}
@@ -1709,7 +1768,7 @@ static void do_embed(Pp* pp, const Tok* line, u32 n, SrcLoc loc) {
compiler_panic(pp->c, loc, "#embed: unexpected token in parameter list");
}
- if (!find_and_open_include(pp, path, system_form, &data, &size, resolved,
+ if (!find_and_open_include(pp, path, system_form, loc, &data, &size, resolved,
sizeof(resolved))) {
compiler_panic(pp->c, loc, "#embed: file not found: %s", path);
}