diff --git a/lld/ELF/ScriptLexer.cpp b/lld/ELF/ScriptLexer.cpp index 40c46370043520..d3a1586332f995 100644 --- a/lld/ELF/ScriptLexer.cpp +++ b/lld/ELF/ScriptLexer.cpp @@ -36,12 +36,13 @@ using namespace llvm; using namespace lld; using namespace lld::elf; +ScriptLexer::ScriptLexer(MemoryBufferRef mb) : curBuf(mb), mbs(1, mb) {} + // Returns a whole line containing the current token. StringRef ScriptLexer::getLine() { StringRef s = getCurrentMB().getBuffer(); - StringRef tok = tokens[pos - 1]; - size_t pos = s.rfind('\n', tok.data() - s.data()); + size_t pos = s.rfind('\n', prevTok.data() - s.data()); if (pos != StringRef::npos) s = s.substr(pos + 1); return s.substr(0, s.find_first_of("\r\n")); @@ -49,11 +50,10 @@ StringRef ScriptLexer::getLine() { // Returns 1-based line number of the current token. size_t ScriptLexer::getLineNumber() { - if (pos == 0) + if (prevTok.empty()) return 1; StringRef s = getCurrentMB().getBuffer(); - StringRef tok = tokens[pos - 1]; - const size_t tokOffset = tok.data() - s.data(); + const size_t tokOffset = prevTok.data() - s.data(); // For the first token, or when going backwards, start from the beginning of // the buffer. If this token is after the previous token, start from the @@ -76,8 +76,7 @@ size_t ScriptLexer::getLineNumber() { // Returns 0-based column number of the current token. size_t ScriptLexer::getColumnNumber() { - StringRef tok = tokens[pos - 1]; - return tok.data() - getLine().data(); + return prevTok.data() - getLine().data(); } std::string ScriptLexer::getCurrentLocation() { @@ -85,31 +84,33 @@ std::string ScriptLexer::getCurrentLocation() { return (filename + ":" + Twine(getLineNumber())).str(); } -ScriptLexer::ScriptLexer(MemoryBufferRef mb) { tokenize(mb); } - // We don't want to record cascading errors. Keep only the first one. void ScriptLexer::setError(const Twine &msg) { if (errorCount()) return; std::string s = (getCurrentLocation() + ": " + msg).str(); - if (pos) + if (prevTok.size()) s += "\n>>> " + getLine().str() + "\n>>> " + std::string(getColumnNumber(), ' ') + "^"; error(s); } -// Split S into linker script tokens. -void ScriptLexer::tokenize(MemoryBufferRef mb) { - std::vector vec; - mbs.push_back(mb); - StringRef s = mb.getBuffer(); - StringRef begin = s; - +void ScriptLexer::lex() { for (;;) { + StringRef &s = curBuf.s; s = skipSpace(s); - if (s.empty()) - break; + if (s.empty()) { + // If this buffer is from an INCLUDE command, switch to the "return + // value"; otherwise, mark EOF. + if (buffers.empty()) { + eof = true; + return; + } + curBuf = buffers.pop_back_val(); + continue; + } + curTokState = inExpr; // Quoted token. Note that double-quote characters are parts of a token // because, in a glob match context, only unquoted tokens are interpreted @@ -118,45 +119,53 @@ void ScriptLexer::tokenize(MemoryBufferRef mb) { if (s.starts_with("\"")) { size_t e = s.find("\"", 1); if (e == StringRef::npos) { - StringRef filename = mb.getBufferIdentifier(); - size_t lineno = begin.substr(0, s.data() - begin.data()).count('\n'); - error(filename + ":" + Twine(lineno + 1) + ": unclosed quote"); + size_t lineno = + StringRef(curBuf.begin, s.data() - curBuf.begin).count('\n'); + error(curBuf.filename + ":" + Twine(lineno + 1) + ": unclosed quote"); return; } - vec.push_back(s.take_front(e + 1)); + curTok = s.take_front(e + 1); s = s.substr(e + 1); - continue; + return; } // Some operators form separate tokens. if (s.starts_with("<<=") || s.starts_with(">>=")) { - vec.push_back(s.substr(0, 3)); + curTok = s.substr(0, 3); s = s.substr(3); - continue; + return; } - if (s.size() > 1 && ((s[1] == '=' && strchr("*/+-<>&^|", s[0])) || - (s[0] == s[1] && strchr("<>&|", s[0])))) { - vec.push_back(s.substr(0, 2)); + if (s.size() > 1 && (s[1] == '=' && strchr("+-*/!&^|", s[0]))) { + curTok = s.substr(0, 2); s = s.substr(2); - continue; + return; } - // Unquoted token. This is more relaxed than tokens in C-like language, - // so that you can write "file-name.cpp" as one bare token, for example. - size_t pos = s.find_first_not_of( - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - "0123456789_.$/\\~=+[]*?-!^:"); + // Unquoted token. The non-expression token is more relaxed than tokens in + // C-like languages, so that you can write "file-name.cpp" as one bare + // token. + size_t pos; + if (inExpr) { + pos = s.find_first_not_of( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789_.$"); + if (pos == 0 && s.size() >= 2 && + ((s[0] == s[1] && strchr("<>&|", s[0])) || + is_contained({"==", "!=", "<=", ">=", "<<", ">>"}, s.substr(0, 2)))) + pos = 2; + } else { + pos = s.find_first_not_of( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789_.$/\\~=+[]*?-!^:"); + } - // A character that cannot start a word (which is usually a - // punctuation) forms a single character token. if (pos == 0) pos = 1; - vec.push_back(s.substr(0, pos)); + curTok = s.substr(0, pos); s = s.substr(pos); + break; } - - tokens.insert(tokens.begin() + pos, vec.begin(), vec.end()); } // Skip leading whitespace characters or comments. @@ -185,93 +194,30 @@ StringRef ScriptLexer::skipSpace(StringRef s) { } } -// An erroneous token is handled as if it were the last token before EOF. -bool ScriptLexer::atEOF() { return errorCount() || tokens.size() == pos; } - -// Split a given string as an expression. -// This function returns "3", "*" and "5" for "3*5" for example. -static std::vector tokenizeExpr(StringRef s) { - StringRef ops = "!~*/+-<>?^:="; // List of operators - - // Quoted strings are literal strings, so we don't want to split it. - if (s.starts_with("\"")) - return {s}; - - // Split S with operators as separators. - std::vector ret; - while (!s.empty()) { - size_t e = s.find_first_of(ops); - - // No need to split if there is no operator. - if (e == StringRef::npos) { - ret.push_back(s); - break; - } - - // Get a token before the operator. - if (e != 0) - ret.push_back(s.substr(0, e)); - - // Get the operator as a token. - // Keep !=, ==, >=, <=, << and >> operators as a single tokens. - if (s.substr(e).starts_with("!=") || s.substr(e).starts_with("==") || - s.substr(e).starts_with(">=") || s.substr(e).starts_with("<=") || - s.substr(e).starts_with("<<") || s.substr(e).starts_with(">>")) { - ret.push_back(s.substr(e, 2)); - s = s.substr(e + 2); - } else { - ret.push_back(s.substr(e, 1)); - s = s.substr(e + 1); - } - } - return ret; -} - -// In contexts where expressions are expected, the lexer should apply -// different tokenization rules than the default one. By default, -// arithmetic operator characters are regular characters, but in the -// expression context, they should be independent tokens. -// -// For example, "foo*3" should be tokenized to "foo", "*" and "3" only -// in the expression context. -// -// This function may split the current token into multiple tokens. -void ScriptLexer::maybeSplitExpr() { - if (!inExpr || errorCount() || atEOF()) - return; - - std::vector v = tokenizeExpr(tokens[pos]); - if (v.size() == 1) - return; - tokens.erase(tokens.begin() + pos); - tokens.insert(tokens.begin() + pos, v.begin(), v.end()); -} +// Used to determine whether to stop parsing. Treat errors like EOF. +bool ScriptLexer::atEOF() { return eof || errorCount(); } StringRef ScriptLexer::next() { - maybeSplitExpr(); - - if (errorCount()) - return ""; - if (atEOF()) { - setError("unexpected EOF"); - return ""; - } - return tokens[pos++]; + prevTok = peek(); + return std::exchange(curTok, StringRef(curBuf.s.data(), 0)); } StringRef ScriptLexer::peek() { - StringRef tok = next(); - if (errorCount()) - return ""; - pos = pos - 1; - return tok; + // curTok is invalid if curTokState and inExpr mismatch. + if (curTok.size() && curTokState != inExpr) { + curBuf.s = StringRef(curTok.data(), curBuf.s.end() - curTok.data()); + curTok = {}; + } + if (curTok.empty()) + lex(); + return curTok; } bool ScriptLexer::consume(StringRef tok) { - if (next() == tok) - return true; - --pos; - return false; + if (peek() != tok) + return false; + next(); + return true; } void ScriptLexer::skip() { (void)next(); } @@ -280,8 +226,12 @@ void ScriptLexer::expect(StringRef expect) { if (errorCount()) return; StringRef tok = next(); - if (tok != expect) - setError(expect + " expected, but got " + tok); + if (tok != expect) { + if (atEOF()) + setError("unexpected EOF"); + else + setError(expect + " expected, but got " + tok); + } } // Returns true if S encloses T. @@ -292,10 +242,8 @@ static bool encloses(StringRef s, StringRef t) { MemoryBufferRef ScriptLexer::getCurrentMB() { // Find input buffer containing the current token. assert(!mbs.empty()); - if (pos == 0) - return mbs.back(); for (MemoryBufferRef mb : mbs) - if (encloses(mb.getBuffer(), tokens[pos - 1])) + if (encloses(mb.getBuffer(), curBuf.s)) return mb; llvm_unreachable("getCurrentMB: failed to find a token"); } diff --git a/lld/ELF/ScriptLexer.h b/lld/ELF/ScriptLexer.h index 7d945d8f570c36..7651908d60200b 100644 --- a/lld/ELF/ScriptLexer.h +++ b/lld/ELF/ScriptLexer.h @@ -10,6 +10,7 @@ #define LLD_ELF_SCRIPT_LEXER_H #include "lld/Common/LLVM.h" +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/MemoryBufferRef.h" #include @@ -17,11 +18,35 @@ namespace lld::elf { class ScriptLexer { +protected: + struct Buffer { + // The remaining content to parse and the filename. + StringRef s, filename; + const char *begin = nullptr; + Buffer() = default; + Buffer(MemoryBufferRef mb) + : s(mb.getBuffer()), filename(mb.getBufferIdentifier()), + begin(mb.getBufferStart()) {} + }; + // The current buffer and parent buffers due to INCLUDE. + Buffer curBuf; + SmallVector buffers; + + // The token before the last next(). + StringRef prevTok; + // Rules for what is a token are different when we are in an expression. + // curTok holds the cached return value of peek() and is invalid when the + // expression state changes. + StringRef curTok; + // The inExpr state when curTok is cached. + bool curTokState = false; + bool eof = false; + public: explicit ScriptLexer(MemoryBufferRef mb); void setError(const Twine &msg); - void tokenize(MemoryBufferRef mb); + void lex(); StringRef skipSpace(StringRef s); bool atEOF(); StringRef next(); @@ -33,15 +58,12 @@ class ScriptLexer { MemoryBufferRef getCurrentMB(); std::vector mbs; - std::vector tokens; bool inExpr = false; - size_t pos = 0; size_t lastLineNumber = 0; size_t lastLineNumberOffset = 0; private: - void maybeSplitExpr(); StringRef getLine(); size_t getLineNumber(); size_t getColumnNumber(); diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp index 8637a8b0b2167e..7303bb564afad8 100644 --- a/lld/ELF/ScriptParser.cpp +++ b/lld/ELF/ScriptParser.cpp @@ -200,8 +200,9 @@ void ScriptParser::readDynamicList() { std::tie(locals, globals) = readSymbols(); expect(";"); - if (!atEOF()) { - setError("EOF expected, but got " + next()); + StringRef tok = peek(); + if (tok.size()) { + setError("EOF expected, but got " + tok); return; } if (!locals.empty()) { @@ -215,8 +216,9 @@ void ScriptParser::readDynamicList() { void ScriptParser::readVersionScript() { readVersionScriptCommand(); - if (!atEOF()) - setError("EOF expected, but got " + next()); + StringRef tok = peek(); + if (tok.size()) + setError("EOF expected, but got " + tok); } void ScriptParser::readVersionScriptCommand() { @@ -225,7 +227,9 @@ void ScriptParser::readVersionScriptCommand() { return; } - while (!atEOF() && !errorCount() && peek() != "}") { + if (atEOF()) + setError("unexpected EOF"); + while (peek() != "}" && !atEOF()) { StringRef verStr = next(); if (verStr == "{") { setError("anonymous version definition is used in " @@ -246,6 +250,8 @@ void ScriptParser::readVersion() { void ScriptParser::readLinkerScript() { while (!atEOF()) { StringRef tok = next(); + if (atEOF()) + break; if (tok == ";") continue; @@ -307,8 +313,9 @@ void ScriptParser::readDefsym(StringRef name) { void ScriptParser::readNoCrossRefs(bool to) { expect("("); NoCrossRefCommand cmd{{}, to}; - while (!errorCount() && !consume(")")) + while (peek() != ")" && !atEOF()) cmd.outputSections.push_back(unquote(next())); + expect(")"); if (cmd.outputSections.size() < 2) warn(getCurrentLocation() + ": ignored with fewer than 2 output sections"); else @@ -368,9 +375,10 @@ void ScriptParser::readAsNeeded() { expect("("); bool orig = config->asNeeded; config->asNeeded = true; - while (!errorCount() && !consume(")")) + while (peek() != ")" && !atEOF()) addFile(unquote(next())); config->asNeeded = orig; + expect(")"); } void ScriptParser::readEntry() { @@ -384,8 +392,9 @@ void ScriptParser::readEntry() { void ScriptParser::readExtern() { expect("("); - while (!errorCount() && !consume(")")) + while (peek() != ")" && !atEOF()) config->undefined.push_back(unquote(next())); + expect(")"); } void ScriptParser::readGroup() { @@ -406,8 +415,11 @@ void ScriptParser::readInclude() { } if (std::optional path = searchScript(tok)) { - if (std::optional mb = readFile(*path)) - tokenize(*mb); + if (std::optional mb = readFile(*path)) { + buffers.push_back(curBuf); + curBuf = Buffer(*mb); + mbs.push_back(*mb); + } return; } setError("cannot find linker script " + tok); @@ -415,12 +427,13 @@ void ScriptParser::readInclude() { void ScriptParser::readInput() { expect("("); - while (!errorCount() && !consume(")")) { + while (peek() != ")" && !atEOF()) { if (consume("AS_NEEDED")) readAsNeeded(); else addFile(unquote(next())); } + expect(")"); } void ScriptParser::readOutput() { @@ -435,8 +448,8 @@ void ScriptParser::readOutput() { void ScriptParser::readOutputArch() { // OUTPUT_ARCH is ignored for now. expect("("); - while (!errorCount() && !consume(")")) - skip(); + while (next() != ")" && !atEOF()) + ; } static std::pair parseBfdName(StringRef s) { @@ -702,8 +715,9 @@ static int precedence(StringRef op) { StringMatcher ScriptParser::readFilePatterns() { StringMatcher Matcher; - while (!errorCount() && !consume(")")) + while (peek() != ")" && !atEOF()) Matcher.addPattern(SingleStringMatcher(next())); + expect(")"); return Matcher; } @@ -790,7 +804,7 @@ ScriptParser::readInputSectionRules(StringRef filePattern, uint64_t withFlags, make(filePattern, withFlags, withoutFlags); expect("("); - while (!errorCount() && !consume(")")) { + while (peek() != ")" && !atEOF()) { SortSectionPolicy outer = readSortKind(); SortSectionPolicy inner = SortSectionPolicy::Default; SmallVector v; @@ -816,6 +830,7 @@ ScriptParser::readInputSectionRules(StringRef filePattern, uint64_t withFlags, std::move(v.begin(), v.end(), std::back_inserter(cmd->sectionPatterns)); } + expect(")"); return cmd; } @@ -1098,12 +1113,23 @@ SymbolAssignment *ScriptParser::readProvideHidden(bool provide, bool hidden) { return cmd; } +// Replace whitespace sequence (including \n) with one single space. The output +// is used by -Map. +static void squeezeSpaces(std::string &str) { + char prev = '\0'; + auto it = str.begin(); + for (char c : str) + if (!isSpace(c) || (c = ' ') != prev) + *it++ = prev = c; + str.erase(it, str.end()); +} + SymbolAssignment *ScriptParser::readAssignment(StringRef tok) { // Assert expression returns Dot, so this is equal to ".=." if (tok == "ASSERT") return make(".", readAssert(), 0, getCurrentLocation()); - size_t oldPos = pos; + const char *oldS = prevTok.data(); SymbolAssignment *cmd = nullptr; bool savedSeenRelroEnd = script->seenRelroEnd; const StringRef op = peek(); @@ -1127,9 +1153,8 @@ SymbolAssignment *ScriptParser::readAssignment(StringRef tok) { if (cmd) { cmd->dataSegmentRelroEnd = !savedSeenRelroEnd && script->seenRelroEnd; - cmd->commandString = - tok.str() + " " + - llvm::join(tokens.begin() + oldPos, tokens.begin() + pos, " "); + cmd->commandString = StringRef(oldS, curTok.data() - oldS).str(); + squeezeSpaces(cmd->commandString); expect(";"); } return cmd; @@ -1333,12 +1358,11 @@ ByteCommand *ScriptParser::readByteCommand(StringRef tok) { if (size == -1) return nullptr; - size_t oldPos = pos; + const char *oldS = prevTok.data(); Expr e = readParenExpr(); - std::string commandString = - tok.str() + " " + - llvm::join(tokens.begin() + oldPos, tokens.begin() + pos, " "); - return make(e, size, commandString); + std::string commandString = StringRef(oldS, curBuf.s.data() - oldS).str(); + squeezeSpaces(commandString); + return make(e, size, std::move(commandString)); } static std::optional parseFlag(StringRef tok) { diff --git a/lld/test/ELF/linkerscript/invalid.test b/lld/test/ELF/linkerscript/invalid.test index 4cbedf639cb1a3..73b761ce4d571c 100644 --- a/lld/test/ELF/linkerscript/invalid.test +++ b/lld/test/ELF/linkerscript/invalid.test @@ -15,7 +15,7 @@ # RUN: echo foobar > %t1 # RUN: not ld.lld %t1 no-such-file 2>&1 | FileCheck -check-prefix=ERR1 %s -# ERR1: unexpected EOF +# ERR1: error: {{.*}}1:1: unknown directive: foobar # ERR1: cannot open no-such-file: # RUN: echo "foo \"bar" > %t2 diff --git a/lld/test/ELF/linkerscript/map-file.test b/lld/test/ELF/linkerscript/map-file.test index 6ec8bafc42b161..6347c3a5d900a0 100644 --- a/lld/test/ELF/linkerscript/map-file.test +++ b/lld/test/ELF/linkerscript/map-file.test @@ -7,17 +7,17 @@ # RUN: FileCheck -strict-whitespace %s < %t.map SECTIONS { - . = 0x1000; + . = 0x1000; # tabs .foo : { - BYTE(0x11) - SHORT(0x1122) + BYTE ( 0x11 ) + SHORT (0x1122) LONG(0x11223344) QUAD(0x1122334455667788) PROVIDE_HIDDEN(sym4 = .); . += 0x1000; *(.foo.1) PROVIDE(unused1 = 0xff); - HIDDEN(sym6 = .); + HIDDEN( sym6 = . ); . += 0x123 * (1 + 1); foo = .; @@ -34,20 +34,20 @@ SECTIONS { # CHECK-NEXT: 0 0 1000 1 . = 0x1000 # CHECK-NEXT: 1000 1000 125d 1 .foo # CHECK-NEXT: 1000 1000 1 1 BYTE ( 0x11 ) -# CHECK-NEXT: 1001 1001 2 1 SHORT ( 0x1122 ) -# CHECK-NEXT: 1003 1003 4 1 LONG ( 0x11223344 ) -# CHECK-NEXT: 1007 1007 8 1 QUAD ( 0x1122334455667788 ) -# CHECK-NEXT: 100f 100f 0 1 PROVIDE_HIDDEN ( sym4 = . ) +# CHECK-NEXT: 1001 1001 2 1 SHORT (0x1122) +# CHECK-NEXT: 1003 1003 4 1 LONG(0x11223344) +# CHECK-NEXT: 1007 1007 8 1 QUAD(0x1122334455667788) +# CHECK-NEXT: 100f 100f 0 1 PROVIDE_HIDDEN(sym4 = .) # CHECK-NEXT: 100f 100f 1000 1 . += 0x1000 # CHECK-NEXT: 200f 200f 8 1 {{.*}}{{/|\\}}map-file.test.tmp.o:(.foo.1) -# CHECK-NEXT: 2017 2017 0 1 HIDDEN ( sym6 = . ) -# CHECK-NEXT: 2017 2017 246 1 . += 0x123 * ( 1 + 1 ) +# CHECK-NEXT: 2017 2017 0 1 HIDDEN( sym6 = . ) +# CHECK-NEXT: 2017 2017 246 1 . += 0x123 * (1 + 1) # CHECK-NEXT: 225d 225d 0 1 foo = . # CHECK-NEXT: 225d 225d 0 1 bar = 0x42 - 0x26 # CHECK-NEXT: 225d 225d 0 1 sym1 = . # CHECK-NEXT: 225d 225d 500 1 . += 0x500 # CHECK-NEXT: 275d 275d 0 1 sym2 = . -# CHECK-NEXT: 275d 275d 0 1 PROVIDE ( sym3 = 42 ) +# CHECK-NEXT: 275d 275d 0 1 PROVIDE(sym3 = 42) # CHECK-NEXT: 2760 2760 10 4 .text # CHECK-NEXT: 2760 2760 10 4 {{.*}}{{/|\\}}map-file.test.tmp.o:(.text) # CHECK-NEXT: 0 0 8 1 .comment diff --git a/lld/test/ELF/linkerscript/map-file2.test b/lld/test/ELF/linkerscript/map-file2.test index 8efb5d6cd3d345..a34595f14856ee 100644 --- a/lld/test/ELF/linkerscript/map-file2.test +++ b/lld/test/ELF/linkerscript/map-file2.test @@ -27,7 +27,7 @@ SECTIONS { # CHECK-NEXT: 1010 3000 8 1 {{.*}}{{/|\\}}map-file2.test.tmp.o:(.ccc) # CHECK-NEXT: 1018 3008 100 1 . += 0x100 # CHECK-NEXT: 1118 3108 109 1 .ddd -# CHECK-NEXT: 1118 3108 1 1 BYTE ( 0x11 ) +# CHECK-NEXT: 1118 3108 1 1 BYTE(0x11) # CHECK-NEXT: 1119 3109 100 1 . += 0x100 # CHECK-NEXT: 1219 3209 8 1 {{.*}}{{/|\\}}map-file2.test.tmp.o:(.ddd) # CHECK-NEXT: 1228 3218 34 8 .eh_frame diff --git a/lld/test/ELF/linkerscript/unquoted.test b/lld/test/ELF/linkerscript/unquoted.test index 7dca75fe09ab1f..9a30ae8a37ff7d 100644 --- a/lld/test/ELF/linkerscript/unquoted.test +++ b/lld/test/ELF/linkerscript/unquoted.test @@ -12,11 +12,10 @@ INCLUDE "empty.lds" INCLUDE "1.lds" # RUN: not ld.lld -shared 0.o -T 1.lds 2>&1 | FileCheck %s --check-prefix=CHECK1 --match-full-lines --strict-whitespace -# RUN: not ld.lld -shared 0.o -T 1a.lds 2>&1 | FileCheck %s --check-prefix=CHECK1A --match-full-lines --strict-whitespace -# CHECK1:{{.*}}error: 1.lds:1: unclosed comment in a linker script -# CHECK1A:{{.*}}error: 1a.lds:3: unclosed comment in a linker script -#CHECK1A-NEXT:>>> INCLUDE "1.lds" -#CHECK1A-NEXT:>>> ^ +# RUN: not ld.lld -shared 0.o -T 1a.lds 2>&1 | FileCheck %s --check-prefix=CHECK1 --match-full-lines --strict-whitespace +# CHECK1:{{.*}}error: 1.lds:2: unclosed comment in a linker script +# CHECK1-NEXT:>>> SECTIONS /* +# CHECK1-NEXT:>>> ^ #--- 2.lds INCLUDE "empty.lds"