Repository: cc65/cc65 (2025-07-01T15:26:01Z)

Issue:
cc65: Comments within _Pragma aren't accepted
This should work, but doesn't:

```C
_Pragma("warn(remap-zero, /* Comment */ on)")
```

Admittedly, this is rather obscure and may not be worth a fix, but it's a standard violation anyway.


Discussion:
Wow this IS obscure... how do you even find such things? :)
I reread the relevant parts of the standard when looking at PR #2226 and found that this must work. Checked with gcc/clang and they both accept it.

Fun fact: gcc 9.4.0 gets the location wrong, so it's not only cc65 that has problems with obscure stuff like this:

```
test.c:1:27: warning: foo
    1 | _Pragma("GCC warning /* Comment */ \"foo\"")
      |                           ^~~~~
```
Column 27 would be the correct column without "_Pragma" and unescaping the string.



Patch:
diff --git a/src/cc65/pragma.c b/src/cc65/pragma.c
index 49754fbe8d..51f7f3007f 100644
--- a/src/cc65/pragma.c
+++ b/src/cc65/pragma.c
@@ -48,6 +48,7 @@
 #include "error.h"
 #include "global.h"
 #include "litpool.h"
+#include "preproc.h"
 #include "scanner.h"
 #include "scanstrbuf.h"
 #include "symtab.h"
@@ -866,24 +867,18 @@ static void NoteMessagePragma (const char* Message)
 
 
 
-static void ParsePragmaString (void)
+static void ParsePragmaString (StrBuf* B)
 /* Parse the contents of _Pragma */
 {
     pragma_t Pragma;
     StrBuf   Ident = AUTO_STRBUF_INITIALIZER;
 
-    /* Create a string buffer from the string literal */
-    StrBuf B = AUTO_STRBUF_INITIALIZER;
-
-
-    SB_Append (&B, GetLiteralStrBuf (CurTok.SVal));
-
     /* Skip the string token */
     NextToken ();
 
     /* Get the pragma name from the string */
-    SB_SkipWhite (&B);
-    if (!SB_GetSym (&B, &Ident, "-")) {
+    SB_SkipWhite (B);
+    if (!SB_GetSym (B, &Ident, "-")) {
         Error ("Invalid pragma");
         goto ExitPoint;
     }
@@ -903,119 +898,119 @@ static void ParsePragmaString (void)
     }
 
     /* Check for an open paren */
-    SB_SkipWhite (&B);
-    if (SB_Get (&B) != '(') {
+    SB_SkipWhite (B);
+    if (SB_Get (B) != '(') {
         Error ("'(' expected");
         goto ExitPoint;
     }
 
     /* Skip white space before the argument */
-    SB_SkipWhite (&B);
+    SB_SkipWhite (B);
 
     /* Switch for the different pragmas */
     switch (Pragma) {
 
         case PRAGMA_ALIGN:
             /* TODO: PES_EXPR (PES_DECL) */
-            IntPragma (PES_STMT, Pragma, &B, &DataAlignment, 1, 4096);
+            IntPragma (PES_STMT, Pragma, B, &DataAlignment, 1, 4096);
             break;
 
         case PRAGMA_ALLOW_EAGER_INLINE:
-            FlagPragma (PES_STMT, Pragma, &B, &EagerlyInlineFuncs);
+            FlagPragma (PES_STMT, Pragma, B, &EagerlyInlineFuncs);
             break;
 
         case PRAGMA_BSS_NAME:
             /* TODO: PES_STMT or even PES_EXPR (PES_DECL) maybe? */
-            SegNamePragma (PES_FUNC, PRAGMA_BSS_NAME, &B);
+            SegNamePragma (PES_FUNC, PRAGMA_BSS_NAME, B);
             break;
 
         case PRAGMA_CHARMAP:
-            CharMapPragma (PES_IMM, &B);
+            CharMapPragma (PES_IMM, B);
             break;
 
         case PRAGMA_CHECK_STACK:
             /* TODO: PES_SCOPE maybe? */
-            FlagPragma (PES_FUNC, Pragma, &B, &CheckStack);
+            FlagPragma (PES_FUNC, Pragma, B, &CheckStack);
             break;
 
         case PRAGMA_CODE_NAME:
             /* PES_FUNC is the only sensible option so far */
-            SegNamePragma (PES_FUNC, PRAGMA_CODE_NAME, &B);
+            SegNamePragma (PES_FUNC, PRAGMA_CODE_NAME, B);
             break;
 
         case PRAGMA_CODESIZE:
             /* PES_EXPR would be optimization nightmare */
-            IntPragma (PES_STMT, Pragma, &B, &CodeSizeFactor, 10, 1000);
+            IntPragma (PES_STMT, Pragma, B, &CodeSizeFactor, 10, 1000);
             break;
 
         case PRAGMA_DATA_NAME:
             /* TODO: PES_STMT or even PES_EXPR (PES_DECL) maybe? */
-            SegNamePragma (PES_FUNC, PRAGMA_DATA_NAME, &B);
+            SegNamePragma (PES_FUNC, PRAGMA_DATA_NAME, B);
             break;
 
         case PRAGMA_INLINE_STDFUNCS:
             /* TODO: PES_EXPR maybe? */
-            FlagPragma (PES_STMT, Pragma, &B, &InlineStdFuncs);
+            FlagPragma (PES_STMT, Pragma, B, &InlineStdFuncs);
             break;
 
         case PRAGMA_LOCAL_STRINGS:
             /* TODO: PES_STMT or even PES_EXPR */
-            FlagPragma (PES_FUNC, Pragma, &B, &LocalStrings);
+            FlagPragma (PES_FUNC, Pragma, B, &LocalStrings);
             break;
 
         case PRAGMA_MESSAGE:
             /* PES_IMM is the only sensible option */
-            StringPragma (PES_IMM, &B, NoteMessagePragma);
+            StringPragma (PES_IMM, B, NoteMessagePragma);
             break;
 
         case PRAGMA_OPTIMIZE:
             /* TODO: PES_STMT or even PES_EXPR maybe? */
-            FlagPragma (PES_STMT, Pragma, &B, &Optimize);
+            FlagPragma (PES_STMT, Pragma, B, &Optimize);
             break;
 
         case PRAGMA_REGVARADDR:
             /* TODO: PES_STMT or even PES_EXPR maybe? */
-            FlagPragma (PES_FUNC, Pragma, &B, &AllowRegVarAddr);
+            FlagPragma (PES_FUNC, Pragma, B, &AllowRegVarAddr);
             break;
 
         case PRAGMA_REGISTER_VARS:
             /* TODO: PES_STMT or even PES_EXPR (PES_DECL) maybe? */
-            FlagPragma (PES_FUNC, Pragma, &B, &EnableRegVars);
+            FlagPragma (PES_FUNC, Pragma, B, &EnableRegVars);
             break;
 
         case PRAGMA_RODATA_NAME:
             /* TODO: PES_STMT or even PES_EXPR maybe? */
-            SegNamePragma (PES_FUNC, PRAGMA_RODATA_NAME, &B);
+            SegNamePragma (PES_FUNC, PRAGMA_RODATA_NAME, B);
             break;
 
         case PRAGMA_SIGNED_CHARS:
             /* TODO: PES_STMT or even PES_EXPR maybe? */
-            FlagPragma (PES_FUNC, Pragma, &B, &SignedChars);
+            FlagPragma (PES_FUNC, Pragma, B, &SignedChars);
             break;
 
         case PRAGMA_STATIC_LOCALS:
             /* TODO: PES_STMT or even PES_EXPR (PES_DECL) maybe? */
-            FlagPragma (PES_FUNC, Pragma, &B, &StaticLocals);
+            FlagPragma (PES_FUNC, Pragma, B, &StaticLocals);
             break;
 
         case PRAGMA_WRAPPED_CALL:
             /* PES_IMM is the only sensible option */
-            WrappedCallPragma (PES_IMM, &B);
+            WrappedCallPragma (PES_IMM, B);
             break;
 
         case PRAGMA_WARN:
             /* PES_IMM is the only sensible option */
-            WarnPragma (PES_IMM, &B);
+            WarnPragma (PES_IMM, B);
             break;
 
         case PRAGMA_WRITABLE_STRINGS:
             /* TODO: PES_STMT or even PES_EXPR maybe? */
-            FlagPragma (PES_FUNC, Pragma, &B, &WritableStrings);
+            FlagPragma (PES_FUNC, Pragma, B, &WritableStrings);
             break;
 
         case PRAGMA_ZPSYM:
             /* PES_IMM is the only sensible option */
-            StringPragma (PES_IMM, &B, MakeZPSym);
+            StringPragma (PES_IMM, B, MakeZPSym);
             break;
 
         default:
@@ -1023,27 +1018,26 @@ static void ParsePragmaString (void)
     }
 
     /* Closing paren expected */
-    SB_SkipWhite (&B);
-    if (SB_Get (&B) != ')') {
+    SB_SkipWhite (B);
+    if (SB_Get (B) != ')') {
         Error ("')' expected");
         goto ExitPoint;
     }
-    SB_SkipWhite (&B);
+    SB_SkipWhite (B);
 
     /* Allow an optional semicolon to be compatible with the old syntax */
-    if (SB_Peek (&B) == ';') {
-        SB_Skip (&B);
-        SB_SkipWhite (&B);
+    if (SB_Peek (B) == ';') {
+        SB_Skip (B);
+        SB_SkipWhite (B);
     }
 
     /* Make sure nothing follows */
-    if (SB_Peek (&B) != '\0') {
+    if (SB_Peek (B) != '\0') {
         Error ("Unexpected input following pragma directive");
     }
 
 ExitPoint:
     /* Release the string buffers */
-    SB_Done (&B);
     SB_Done (&Ident);
 }
 
@@ -1082,8 +1076,24 @@ void ConsumePragma (void)
         */
         PragmaErrorSkip ();
     } else {
+        /* Pragmas that have the C99 _Pragma operator in the source code
+        ** (instead of #pragma that was converted to _Pragma by the
+        ** preprocessor) have the argument not preprocessed. We need to do
+        ** that here, since it may contain comments. Weird but legal C. Pragmas
+        ** that were converted from #pragma are already preprocessed but doing
+        ** it twice won't harm.
+        */
+        StrBuf In  = AUTO_STRBUF_INITIALIZER;
+        StrBuf Out = AUTO_STRBUF_INITIALIZER;
+        SB_Append (&In, GetLiteralStrBuf (CurTok.SVal));
+        TranslationPhase3 (&In, &Out);
+        SB_Done (&In);
+
         /* Parse the pragma */
-        ParsePragmaString ();
+        ParsePragmaString (&Out);
+
+        /* Free the string buffer */
+        SB_Done (&Out);
     }
 
     --InPragmaParser;
diff --git a/src/cc65/preproc.c b/src/cc65/preproc.c
index bc21a69d1a..bc3b532fa7 100644
--- a/src/cc65/preproc.c
+++ b/src/cc65/preproc.c
@@ -157,11 +157,6 @@ struct HiddenMacro {
 
 
 
-static void TranslationPhase3 (StrBuf* Source, StrBuf* Target);
-/* Mimic Translation Phase 3. Handle old and new style comments. Collapse
-** non-newline whitespace sequences.
-*/
-
 static void PreprocessDirective (StrBuf* Source, StrBuf* Target, unsigned ModeFlags);
 /* Preprocess a single line. Handle specified tokens and operators, remove
 ** whitespace and comments, then do macro replacement.
@@ -3338,7 +3333,22 @@ void HandleSpecialMacro (Macro* M, const char* Name)
 
 
 
-static void TranslationPhase3 (StrBuf* Source, StrBuf* Target)
+static void PreprocessDirective (StrBuf* Source, StrBuf* Target, unsigned ModeFlags)
+/* Preprocess a single line. Handle specified tokens and operators, remove
+** whitespace and comments, then do macro replacement.
+*/
+{
+    MacroExp    E;
+
+    SkipWhitespace (0);
+    InitMacroExp (&E);
+    ReplaceMacros (Source, Target, &E, ModeFlags | MSM_IN_DIRECTIVE);
+    DoneMacroExp (&E);
+}
+
+
+
+void TranslationPhase3 (StrBuf* Source, StrBuf* Target)
 /* Mimic Translation Phase 3. Handle old and new style comments. Collapse
 ** non-newline whitespace sequences.
 */
@@ -3384,21 +3394,6 @@ static void TranslationPhase3 (StrBuf* Source, StrBuf* Target)
 
 
 
-static void PreprocessDirective (StrBuf* Source, StrBuf* Target, unsigned ModeFlags)
-/* Preprocess a single line. Handle specified tokens and operators, remove
-** whitespace and comments, then do macro replacement.
-*/
-{
-    MacroExp    E;
-
-    SkipWhitespace (0);
-    InitMacroExp (&E);
-    ReplaceMacros (Source, Target, &E, ModeFlags | MSM_IN_DIRECTIVE);
-    DoneMacroExp (&E);
-}
-
-
-
 void Preprocess (void)
 /* Preprocess lines count of which is affected by directives */
 {
diff --git a/src/cc65/preproc.h b/src/cc65/preproc.h
index 44e35f30e2..c8fc67ddc0 100644
--- a/src/cc65/preproc.h
+++ b/src/cc65/preproc.h
@@ -68,6 +68,11 @@ struct IFile;
 void HandleSpecialMacro (Macro* M, const char* Name);
 /* Handle special "magic" macros that may change */
 
+void TranslationPhase3 (StrBuf* Source, StrBuf* Target);
+/* Mimic Translation Phase 3. Handle old and new style comments. Collapse
+** non-newline whitespace sequences.
+*/
+
 void Preprocess (void);
 /* Preprocess a line */
 


Test patch:
diff --git a/test/val/bug2231.c b/test/val/bug2231.c
new file mode 100644
index 0000000000..96a856a8dc
--- /dev/null
+++ b/test/val/bug2231.c
@@ -0,0 +1,10 @@
+_Pragma("message/*Comment1*/ ( /*Comment2*/\"test message\" /*Comment3*/)")
+/* We have no pragma without parenthesis but if there would be one, the
+** following should also work:
+*/
+/* _Pragma("once// Comment") */
+
+int main(void)
+{
+    return 0;
+}