@@ -64,19 +64,23 @@ struct action {
6464 const char* role = "source"; // "source" | "check" | "artifact"
6565 const char* description = "";
6666 bool blocking = false; // check only: gate compilation on it
67- action& input(const char* p) { add(inputs_, p); return *this; }
68- action& output(const char* p) { add(outputs_, p); return *this; }
69- action& arg(const char* a) { add(command_, a); return *this; }
67+ action& input(const char* p) { add(inputs_, sizeof inputs_, p); return *this; }
68+ action& output(const char* p) { add(outputs_, sizeof outputs_, p); return *this; }
69+ action& arg(const char* a) { add(command_, sizeof command_, a); return *this; }
7070 // Declare what a generated MODULE INTERFACE provides/imports. Same
7171 // "declare instead of discover" trade [modules].scan_overrides makes, and
7272 // what lets a generated .cppm exist as a graph node at all.
73- action& provides(const char* n) { add(provides_, n); return *this; }
74- action& imports(const char* n) { add(imports_, n); return *this; }
73+ action& provides(const char* n) { add(provides_, sizeof provides_, n); return *this; }
74+ action& imports(const char* n) { add(imports_, sizeof imports_, n); return *this; }
7575 void submit() const {
7676 std::printf("mcpp:action={\"id\":"); esc(id);
7777 std::printf(",\"role\":"); esc(role);
7878 std::printf(",\"description\":"); esc(description);
7979 std::printf(",\"blocking\":%s", blocking ? "true" : "false");
80+ // A truncated argv would otherwise be INVALID rather than obviously
81+ // wrong — the engine turns this marker into a diagnostic that names
82+ // the limit, instead of a generic "malformed action".
83+ if (overflow_) std::printf(",\"overflow\":true");
8084 std::printf(",\"inputs\":[%s]", inputs_);
8185 std::printf(",\"outputs\":[%s]", outputs_);
8286 std::printf(",\"command\":[%s]", command_);
@@ -85,26 +89,42 @@ struct action {
8589 std::printf("}\n");
8690 }
8791private:
88- char inputs_[4096]{}, outputs_[4096]{}, command_[8192]{}, provides_[1024]{}, imports_[1024]{};
92+ // Fixed buffers because this module must stay buildable BEFORE a std BMI
93+ // exists (it is what a build.mcpp imports, and it may be compiled first) —
94+ // so no std::string. Sizes chosen for real generator invocations: a protoc
95+ // command line with many -I paths runs long.
96+ char inputs_[8192]{}, outputs_[8192]{}, command_[16384]{},
97+ provides_[2048]{}, imports_[2048]{};
98+ mutable bool overflow_ = false;
8999 static void esc(const char* s) {
90100 std::putchar('"');
91101 for (const char* p = s; *p; ++p) {
92- if (*p == '"' || *p == '\\') std::putchar('\\');
93- if (*p == '\n') { std::printf("\\n"); continue; }
94- std::putchar(*p);
102+ unsigned char c = (unsigned char)*p;
103+ if (c == '"' || c == '\\') { std::putchar('\\'); std::putchar(c); continue; }
104+ // Any control character has to be escaped or the payload is not
105+ // JSON at all. \n was handled before; \t and \r reach this code
106+ // through ordinary Windows paths and log text.
107+ if (c < 0x20) { std::printf("\\u%04x", c); continue; }
108+ std::putchar(c);
95109 }
96110 std::putchar('"');
97111 }
98- static void add(char* buf, const char* s) {
112+ // Capacity is a PARAMETER. The previous revision hardcoded 4096 while the
113+ // smallest buffer here was 1024 — a bound living somewhere other than next
114+ // to the array it bounds is exactly the shape that overflows.
115+ bool add(char* buf, unsigned long cap, const char* s) {
99116 unsigned long o = 0; while (buf[o]) ++o;
117+ if (o + 4 >= cap) { overflow_ = true; return false; }
100118 if (o) buf[o++] = ',';
101119 buf[o++] = '"';
102- for (const char* p = s; *p && o + 3 < 4096; ++p) {
120+ for (const char* p = s; *p; ++p) {
121+ if (o + 3 >= cap) { buf[o] = 0; overflow_ = true; return false; }
103122 if (*p == '"' || *p == '\\') buf[o++] = '\\';
104123 buf[o++] = *p;
105124 }
106125 buf[o++] = '"';
107126 buf[o] = 0;
127+ return true;
108128 }
109129};
110130inline void rerun_if_changed(const char* path) { std::printf("mcpp:rerun-if-changed=%s\n", path); }
@@ -350,8 +370,12 @@ build_host_module(const fs::path& bdir, const fs::path& compiler,
350370 " (src/<name>.cppm or [lib] path)." ,
351371 logicalName, interfacePath.string ()));
352372 }
353- // A filesystem-safe stem: a module name contains dots, which are fine in a
354- // path but make `foo.rules.o` read as an extension chain.
373+ // A filesystem-safe stem. Partition separators and any path separator that
374+ // sneaks into a logical name would otherwise create directories that do
375+ // not exist. Dots are left ALONE on purpose: `a.b.rules.o` is a legal
376+ // filename, GCC's own gcm.cache uses the dotted module name verbatim, and
377+ // rewriting them would make the object name disagree with the BMI name for
378+ // no gain.
355379 std::string stem (logicalName);
356380 for (auto & c : stem) if (c == ' :' || c == ' /' || c == ' \\ ' ) c = ' -' ;
357381
0 commit comments