fix(opdf): escape string-constant values in the recConstant .ascii line
The OPDF emitter wrote a string constant's value verbatim into the debug section's `.ascii "<val>" # Value` line. Any double-quote, newline or backslash in the value broke the line: an unescaped `"` closed the literal early, leaving `..." # Value` as a garbage mnemonic, and a newline split the directive across two lines. Under --debug-opdf the OPDF section is appended to the same assembly text the assembler consumes, so this aborted the compile — the internal assembler with "unhandled mnemonic: [\" # Value]" and the external assembler with "junk at end of line". It was content-specific, so it only surfaced on larger programs (e.g. building a debuggable TestRunner) whose constants happened to contain such characters. Add EscapeAsciiStr (C-style escapes: \" \\ \n \r \t + octal for other non-printables) and apply it to the recConstant value. The OPDF ValueLen field stays the RAW byte count, which `.ascii` with these escapes reproduces exactly, so the emitted byte length is unchanged. Tests: TOPDFTests.TestOPDF_StringConst_SpecialChars_Escaped (IR: the .ascii line is escaped) and TInternalAsmE2ETests.TestDebugOPDF_StringConstSpecialChars_ AssemblesAndRuns (compile+run under --debug-opdf --assembler internal). Both the internal- and external-assembler failure modes are resolved by the one escape. Verified: all four fixpoints green; full suite passes under QBE-built and native-built test runner (4026 tests).
This commit is contained in:
parent
135588502d
commit
9bbcfd8b77
|
|
@ -197,6 +197,44 @@ begin
|
|||
FDone := False;
|
||||
end;
|
||||
|
||||
{ Escape a raw string value for a GNU-as / internal-assembler `.ascii "..."`
|
||||
literal. A string CONSTANT's value is arbitrary user text — it may contain a
|
||||
double-quote, backslash, `#`, or control characters, any of which would break
|
||||
the surrounding `.ascii "<val>" # Value` line (an unescaped `"` closes the
|
||||
literal early, leaving `..." # Value` as a garbage mnemonic). We emit the
|
||||
C-style escapes both assemblers understand. The OPDF ValueLen field is the
|
||||
RAW byte count (Length before escaping), which `.ascii` with these escapes
|
||||
reproduces exactly, so escaping does not change the emitted byte length. }
|
||||
function EscapeAsciiStr(const S: string): string;
|
||||
var
|
||||
I, B: Integer;
|
||||
begin
|
||||
Result := '';
|
||||
for I := 0 to Length(S) - 1 do
|
||||
begin
|
||||
B := OrdAt(S, I) and $FF;
|
||||
case B of
|
||||
Ord('\'): Result := Result + '\\';
|
||||
Ord('"'): Result := Result + '\"';
|
||||
10: Result := Result + '\n';
|
||||
13: Result := Result + '\r';
|
||||
9: Result := Result + '\t';
|
||||
else
|
||||
{ Printable ASCII passes through; anything else (including '#', which is a
|
||||
comment start OUTSIDE a quoted literal but harmless inside one, and any
|
||||
non-printable byte) is emitted as a 3-digit octal escape so the line
|
||||
stays a single well-formed token. }
|
||||
if (B >= 32) and (B < 127) then
|
||||
Result := Result + Chr(B)
|
||||
else
|
||||
Result := Result + '\' +
|
||||
Chr(Ord('0') + ((B shr 6) and 7)) +
|
||||
Chr(Ord('0') + ((B shr 3) and 7)) +
|
||||
Chr(Ord('0') + (B and 7));
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TOPDFEmitter.ActiveSymTable: TSymbolTable;
|
||||
begin
|
||||
if FUnit <> nil then
|
||||
|
|
@ -1144,7 +1182,7 @@ begin
|
|||
L(' .word ' + IntToStr(Length(C.StrVal)) + ' # ValueLen');
|
||||
EmitNameLen(C.Name);
|
||||
if Length(C.StrVal) > 0 then
|
||||
L(' .ascii "' + C.StrVal + '" # Value');
|
||||
L(' .ascii "' + EscapeAsciiStr(C.StrVal) + '" # Value');
|
||||
EmitNameData(C.Name);
|
||||
end
|
||||
else
|
||||
|
|
|
|||
|
|
@ -121,6 +121,13 @@ type
|
|||
assembled verbatim by the internal assembler and called from Pascal. }
|
||||
procedure TestInlineAsm_ReturnsValue;
|
||||
procedure TestInlineAsm_AddsTwoArgs;
|
||||
{ --debug-opdf appends the OPDF section to the SAME assembly text the
|
||||
internal assembler consumes. A string constant whose value contains a
|
||||
double-quote or newline used to break the recConstant `.ascii "<val>"`
|
||||
line (the raw value closed the literal early), aborting the assemble.
|
||||
Escaping the value fixes it; this compiles+runs such a program under
|
||||
--debug-opdf --assembler internal. }
|
||||
procedure TestDebugOPDF_StringConstSpecialChars_AssemblesAndRuns;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
|
@ -1326,6 +1333,57 @@ begin
|
|||
AssertEquals('42' + LineEnding, Out_);
|
||||
end;
|
||||
|
||||
procedure TInternalAsmE2ETests.TestDebugOPDF_StringConstSpecialChars_AssemblesAndRuns;
|
||||
var
|
||||
SrcFile, OutFile, CompOut, RunOut: string;
|
||||
Rc, EC: Integer;
|
||||
Src: string;
|
||||
begin
|
||||
if not Self.CompilerAvailable() then
|
||||
begin
|
||||
Ignore('<toolchain-missing>');
|
||||
Exit;
|
||||
end;
|
||||
|
||||
{ A double-quote, a newline and a backslash in string-constant VALUES — each
|
||||
would break the recConstant `.ascii` line if emitted unescaped. }
|
||||
Src :=
|
||||
'program test_opdf_qc;' + LineEnding +
|
||||
'const' + LineEnding +
|
||||
' Q = ' + '''' + '"' + '''' + ';' + LineEnding +
|
||||
' NL = ' + '''' + 'a' + '''' + '#10' + '''' + 'b' + '''' + ';' + LineEnding +
|
||||
' BS = ' + '''' + 'a\b' + '''' + ';' + LineEnding +
|
||||
'begin' + LineEnding +
|
||||
' WriteLn(Q);' + LineEnding +
|
||||
' WriteLn(NL);' + LineEnding +
|
||||
' WriteLn(BS)' + LineEnding +
|
||||
'end.';
|
||||
|
||||
FCounter := FCounter + 1;
|
||||
SrcFile := FScratch + 'test_opdf_' + IntToStr(FCounter) + '.pas';
|
||||
OutFile := FScratch + 'test_opdf_' + IntToStr(FCounter);
|
||||
WriteFile(SrcFile, Src);
|
||||
|
||||
Rc := Self.RunProc(FCompiler, [
|
||||
'--source', SrcFile,
|
||||
'--unit-path', FRTLPath,
|
||||
'--unit-path', FStdlibPath,
|
||||
'--output', OutFile,
|
||||
'--backend', 'native',
|
||||
'--assembler', 'internal',
|
||||
'--debug-opdf'
|
||||
], CompOut);
|
||||
{ The bug manifested as an "Internal assembler error: ... unhandled mnemonic"
|
||||
from the mangled .ascii line — a non-zero compile with that text. }
|
||||
if Rc <> 0 then
|
||||
Fail('compile under --debug-opdf failed (rc=' + IntToStr(Rc) + '): ' + CompOut);
|
||||
|
||||
EC := Self.RunProcNoArgs(OutFile, RunOut);
|
||||
AssertEquals(0, EC);
|
||||
AssertEquals('"' + LineEnding + 'a' + LineEnding + 'b' + LineEnding +
|
||||
'a\b' + LineEnding, RunOut);
|
||||
end;
|
||||
|
||||
{ ---- Registration ---- }
|
||||
|
||||
initialization
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ type
|
|||
procedure TestOPDF_Primitive_Double_SubKindFloat;
|
||||
procedure TestOPDF_Primitive_Single_SubKindFloat;
|
||||
procedure TestOPDF_AnsiStr_Record;
|
||||
{ A string constant whose value contains a double-quote, newline or
|
||||
backslash must be C-escaped in the recConstant `.ascii` line, else the
|
||||
literal closes early and the assembler sees a garbage mnemonic. }
|
||||
procedure TestOPDF_StringConst_SpecialChars_Escaped;
|
||||
procedure TestOPDF_GlobalVar_QuadLabel;
|
||||
procedure TestOPDF_GlobalVar_RecType;
|
||||
procedure TestOPDF_Enum_RecType;
|
||||
|
|
@ -224,6 +228,41 @@ begin
|
|||
AssertTrue('Utf8String name emitted', Contains(IR, '.ascii "Utf8String"'));
|
||||
end;
|
||||
|
||||
procedure TOPDFTests.TestOPDF_StringConst_SpecialChars_Escaped;
|
||||
var
|
||||
IR: string;
|
||||
begin
|
||||
{ A string const whose value is a single double-quote must be emitted as
|
||||
`.ascii "\""`, NOT `.ascii """` (which closes the literal after the first
|
||||
quote and leaves `" # Value` as a stray token). }
|
||||
IR := GenOPDF(
|
||||
'''
|
||||
program P;
|
||||
const Q = '"';
|
||||
begin end.
|
||||
''');
|
||||
AssertTrue('quote value is escaped', Contains(IR, '.ascii "\"" # Value'));
|
||||
AssertFalse('no unescaped triple-quote', Contains(IR, '.ascii """ # Value'));
|
||||
|
||||
{ A newline in the value must become \n, not a literal line break. }
|
||||
IR := GenOPDF(
|
||||
'''
|
||||
program P;
|
||||
const NL = 'a'#10'b';
|
||||
begin end.
|
||||
''');
|
||||
AssertTrue('newline escaped as \n', Contains(IR, '.ascii "a\nb" # Value'));
|
||||
|
||||
{ A backslash must be doubled. }
|
||||
IR := GenOPDF(
|
||||
'''
|
||||
program P;
|
||||
const BS = 'a\b';
|
||||
begin end.
|
||||
''');
|
||||
AssertTrue('backslash doubled', Contains(IR, '.ascii "a\\b" # Value'));
|
||||
end;
|
||||
|
||||
procedure TOPDFTests.TestOPDF_GlobalVar_QuadLabel;
|
||||
var
|
||||
IR: string;
|
||||
|
|
|
|||
Loading…
Reference in a new issue