Repository: compiler-explorer/compiler-explorer
Pull request #7328 (2025-01-29 14:45:53)

Rename beman.optional26 to beman.optional
Rename beman.optional26 to beman.optional as per https://github.com/bemanproject/beman/issues/85


Patch:
diff --git a/docs/API.md b/docs/API.md
index e06a6d0fd..ab1a4ac81 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -188,7 +188,8 @@ JSON request corresponds to one of the `formatters.<key>.type` found in
 
 ### `POST /api/format/<formatter>` - perform a formatter run
 
-Formats a piece of code according to the given base style using the provided formatter
+Formats a piece of code according to the given base style using the provided formatter. Be aware that this endpoint only
+accepts JSON (e.g `content-type: application/json`).
 
 Formatters available can be found with `GET /api/formats`
 
diff --git a/etc/config/c++.amazon.properties b/etc/config/c++.amazon.properties
index dfe116499..66df0380b 100644
--- a/etc/config/c++.amazon.properties
+++ b/etc/config/c++.amazon.properties
@@ -3947,7 +3947,7 @@ compiler.qnx800.qnxLicense=/opt/qnx/licenses
 #################################
 #################################
 # Installed libs
-libs=abseil:array:async_simple:belleviews:beman_iterator_interface:beman_optional26:benchmark:benri:blaze:boost:bmulti:brigand:catch2:cccl:cctz:cereal:cmcstl2:cnl:cppcoro:cppitertools:cpptrace:crosscables:ctbignum:cthash:ctre:date:dataframe:dawjson:dlib:doctest:eastl:eigen:enoki:entt:etl:eve:expected_lite:fastor:flux:fmt:gcem:gemmlowp:glaze:glm:gnufs:gnulibbacktrace:gnuexp:googletest:gsl:hdf5:hedley:hfsm:highfive:highway:hotels-template-library:immer:jsoncons:jsoncpp:kiwaku:kokkos:kumi:kvasir:kyosu:lager:lagom:lexy:libassert:libbpf:libguarded:libsimdpp:libuv:llvm:llvmfs:lua:magic_enum:mfem:mimicpp:mlir:mp-coro:mp-units:namedtype:nanorange:nlohmann_json:nsimd:ofw:openssl:outcome:pegtl:pipes:pugixml:pybind11:python:rangesv3:raberu:reactive_plus_plus:scnlib:seastar:seqan3:simde:simdjson:sol2:spdlog:spy:stdexec:strong_type:taojson:taskflow:tbb:thinkcell:tlexpected:toml11:tomlplusplus:trompeloeil:tts:type_safe:unifex:ureact:vcl:xercesc:xsimd:xtensor:xtl:yomm2:zug:cli11:avr-libstdcpp:curl:copperspice:sqlite:ztdcuneicode:ztdencodingtables:ztdidk:ztdstaticcontainers:ztdtext:ztdplatform:qt:quill:pcre2:widberg-defs:jwt-cpp:xieite:option:mdspan
+libs=abseil:array:async_simple:belleviews:beman_iterator_interface:beman_optional:benchmark:benri:blaze:boost:bmulti:brigand:catch2:cccl:cctz:cereal:cmcstl2:cnl:cppcoro:cppitertools:cpptrace:crosscables:ctbignum:cthash:ctre:date:dataframe:dawjson:dlib:doctest:eastl:eigen:enoki:entt:etl:eve:expected_lite:fastor:flux:fmt:gcem:gemmlowp:glaze:glm:gnufs:gnulibbacktrace:gnuexp:googletest:gsl:hdf5:hedley:hfsm:highfive:highway:hotels-template-library:immer:jsoncons:jsoncpp:kiwaku:kokkos:kumi:kvasir:kyosu:lager:lagom:lexy:libassert:libbpf:libguarded:libsimdpp:libuv:llvm:llvmfs:lua:magic_enum:mfem:mimicpp:mlir:mp-coro:mp-units:namedtype:nanorange:nlohmann_json:nsimd:ofw:openssl:outcome:pegtl:pipes:pugixml:pybind11:python:rangesv3:raberu:reactive_plus_plus:scnlib:seastar:seqan3:simde:simdjson:sol2:spdlog:spy:stdexec:strong_type:taojson:taskflow:tbb:thinkcell:tlexpected:toml11:tomlplusplus:trompeloeil:tts:type_safe:unifex:ureact:vcl:xercesc:xsimd:xtensor:xtl:yomm2:zug:cli11:avr-libstdcpp:curl:copperspice:sqlite:ztdcuneicode:ztdencodingtables:ztdidk:ztdstaticcontainers:ztdtext:ztdplatform:qt:quill:pcre2:widberg-defs:jwt-cpp:xieite:option:mdspan
 
 
 libs.abseil.name=Abseil
@@ -3986,11 +3986,11 @@ libs.beman_iterator_interface.staticliblink=beman.iterator_interface
 libs.beman_iterator_interface.versions.trunk.version=trunk
 libs.beman_iterator_interface.versions.trunk.lookupversion=main
 
-libs.beman_optional26.name=beman.optional26
-libs.beman_optional26.versions=trunk
-libs.beman_optional26.url=https://github.com/beman-project/optional26
-libs.beman_optional26.versions.trunk.version=trunk
-libs.beman_optional26.versions.trunk.path=/opt/compiler-explorer/libs/beman_optional26/main/include
+libs.beman_optional.name=beman.optional
+libs.beman_optional.versions=trunk
+libs.beman_optional.url=https://github.com/beman-project/optional
+libs.beman_optional.versions.trunk.version=trunk
+libs.beman_optional.versions.trunk.path=/opt/compiler-explorer/libs/beman_optional/main/include
 
 libs.benchmark.name=Google Benchmark
 libs.benchmark.versions=trunk:120:130:140:141:150:161:162
@@ -4043,7 +4043,7 @@ libs.blaze.versions.38.version=3.8
 libs.blaze.versions.38.path=/opt/compiler-explorer/libs/blaze/v3.8
 
 libs.boost.name=Boost
-libs.boost.versions=164:165:166:167:168:169:170:171:172:173:174:175:176:177:178:179:180:181:182:183:184:185:186
+libs.boost.versions=164:165:166:167:168:169:170:171:172:173:174:175:176:177:178:179:180:181:182:183:184:185:186:187
 libs.boost.url=https://www.boost.org
 libs.boost.versions.164.version=1.64.0
 libs.boost.versions.164.path=/opt/compiler-explorer/libs/boost_1_64_0
@@ -4096,6 +4096,10 @@ libs.boost.versions.186.version=1.86.0
 libs.boost.versions.186.packagedheaders=true
 libs.boost.versions.186.lookupname=boost_bin
 
+libs.boost.versions.187.version=1.87.0
+libs.boost.versions.187.packagedheaders=true
+libs.boost.versions.187.lookupname=boost_bin
+
 libs.bmulti.name=B-Multi
 libs.bmulti.url=https://gitlab.com/correaa/boost-multi
 libs.bmulti.versions=trunk
diff --git a/etc/config/c.amazon.properties b/etc/config/c.amazon.properties
index 7511e62e7..4b33421da 100644
--- a/etc/config/c.amazon.properties
+++ b/etc/config/c.amazon.properties
@@ -3259,7 +3259,7 @@ compiler.cc65_219.options=-I/opt/compiler-explorer/6502/cc65-2.19/share/cc65/inc
 
 #################################
 # sdcc compilers
-group.sdcc.compilers=sdcc400:sdcc410:sdcc420:sdcc430:sdcc440
+group.sdcc.compilers=sdcc400:sdcc410:sdcc420:sdcc430:sdcc440:sdcc450
 group.sdcc.compilerType=sdcc
 group.sdcc.groupName=SDCC
 group.sdcc.baseName=SDCC
@@ -3278,6 +3278,8 @@ compiler.sdcc430.exe=/opt/compiler-explorer/sdcc-4.3.0/bin/sdcc
 compiler.sdcc430.semver=4.3.0
 compiler.sdcc440.exe=/opt/compiler-explorer/sdcc-4.4.0/bin/sdcc
 compiler.sdcc440.semver=4.4.0
+compiler.sdcc450.exe=/opt/compiler-explorer/sdcc-4.5.0/bin/sdcc
+compiler.sdcc450.semver=4.5.0
 
 #################################
 # z88dk
diff --git a/etc/config/cmakescript.amazon.properties b/etc/config/cmakescript.amazon.properties
index 6ba0e3d2a..69e99bfa9 100644
--- a/etc/config/cmakescript.amazon.properties
+++ b/etc/config/cmakescript.amazon.properties
@@ -1,7 +1,7 @@
-compilers=cmake-3_26_1:cmake-3_26_4:cmake-3_27_9:cmake-3_28_0:cmake-3_28_4:cmake-3_29_2:cmake-3_29_20240506_g1ea8fa8
+compilers=cmake-3_26_1:cmake-3_26_4:cmake-3_27_9:cmake-3_28_0:cmake-3_28_4:cmake-3_29_2:cmake-3_29_20240506_g1ea8fa8:cmake-3_30_7:cmake-3_31_5
 isSemVer=true
 baseName=cmake
-defaultCompiler=cmake-3_28_0
+defaultCompiler=cmake-3_31_5
 
 compiler.cmake-3_26_1.exe=/opt/compiler-explorer/cmake-v3.26.1/bin/cmake
 compiler.cmake-3_26_1.semver=3.26.1
@@ -23,3 +23,9 @@ compiler.cmake-3_29_2.semver=3.29.2
 
 compiler.cmake-3_29_20240506_g1ea8fa8.exe=/opt/compiler-explorer/cmake-v3.29.20240506-g1ea8fa8/bin/cmake
 compiler.cmake-3_29_20240506_g1ea8fa8.semver=3.29.20240506-g1ea8fa8
+
+compiler.cmake-3_30_7.exe=/opt/compiler-explorer/cmake-v3.30.7/bin/cmake
+compiler.cmake-3_30_7.semver=3.30.7
+
+compiler.cmake-3_31_5.exe=/opt/compiler-explorer/cmake-v3.31.5/bin/cmake
+compiler.cmake-3_31_5.semver=3.31.5
diff --git a/etc/config/swift.amazon.properties b/etc/config/swift.amazon.properties
index 33075658d..3b18b2e5c 100644
--- a/etc/config/swift.amazon.properties
+++ b/etc/config/swift.amazon.properties
@@ -3,7 +3,7 @@ demangler=/opt/compiler-explorer/swift-6.0.3/usr/bin/swift-demangle
 defaultCompiler=swift603
 objdumper=/opt/compiler-explorer/gcc-snapshot/bin/objdump
 
-group.swift.compilers=swift311:swift402:swift403:swift41:swift411:swift412:swift42:swift50:swift51:swift52:swift53:swift54:swift55:swift56:swift57:swift58:swift59:swift510:swift603:swiftnightly
+group.swift.compilers=swift311:swift402:swift403:swift41:swift411:swift412:swift42:swift50:swift51:swift52:swift53:swift54:swift55:swift56:swift57:swift58:swift59:swift510:swift603:swiftdevsnapshot:swiftnightly
 group.swift.isSemVer=true
 group.swift.baseName=x86-64 swiftc
 compiler.swift311.exe=/opt/compiler-explorer/swift-3.1.1/usr/bin/swiftc
@@ -50,9 +50,11 @@ compiler.swift59.exe=/opt/compiler-explorer/swift-5.9/usr/bin/swiftc
 compiler.swift59.semver=5.9
 compiler.swift510.exe=/opt/compiler-explorer/swift-5.10/usr/bin/swiftc
 compiler.swift510.semver=5.10
+compiler.swift510.alias=switf510nightly
 compiler.swift603.exe=/opt/compiler-explorer/swift-6.0.3/usr/bin/swiftc
 compiler.swift603.semver=6.0.3
-compiler.swift510.alias=switf510nightly
+compiler.swiftdevsnapshot.exe=/opt/compiler-explorer/swift-dev-snapshot/usr/bin/swiftc
+compiler.swiftdevsnapshot.semver=devsnapshot
 compiler.swiftnightly.exe=/opt/compiler-explorer/swift-nightly/usr/bin/swiftc
 compiler.swiftnightly.semver=nightly
 compiler.swiftnightly.isNightly=true
diff --git a/lib/compilers/odin.ts b/lib/compilers/odin.ts
index a5a9157c2..08f132716 100644
--- a/lib/compilers/odin.ts
+++ b/lib/compilers/odin.ts
@@ -98,13 +98,6 @@ export class OdinCompiler extends BaseCompiler {
                     continue;
                 }
 
-                // skip main
-                if (match[1] === 'main') {
-                    outputLines.push(line);
-                    lastLine = line;
-                    continue;
-                }
-
                 // last line already has require?
                 if (lastLine.includes('@require') || lastLine.includes('@(require)')) {
                     outputLines.push(line);
diff --git a/lib/handlers/api/formatting-controller.ts b/lib/handlers/api/formatting-controller.ts
index 3a348ed8a..4cb60977d 100644
--- a/lib/handlers/api/formatting-controller.ts
+++ b/lib/handlers/api/formatting-controller.ts
@@ -41,6 +41,10 @@ export class FormattingController implements HttpController {
 
     /** Handle requests to /api/format/:tool */
     public async format(req: express.Request, res: express.Response) {
+        if (req.headers['content-type'] !== 'application/json') {
+            return res.status(400).json({exit: 1, answer: `Invalid content type, expected application/json`});
+        }
+
         const formatter = this.formattingService.getFormatterById(req.params.tool);
         // Ensure the target formatter exists
         if (formatter === null) {
diff --git a/lib/parsers/asm-parser.ts b/lib/parsers/asm-parser.ts
index 523a7c487..30bd6e763 100644
--- a/lib/parsers/asm-parser.ts
+++ b/lib/parsers/asm-parser.ts
@@ -32,7 +32,7 @@ import {
     ParsedAsmResultLine,
 } from '../../types/asmresult/asmresult.interfaces.js';
 import {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
-import {assert} from '../assert.js';
+import {assert, unwrap} from '../assert.js';
 import {PropertyGetter} from '../properties.interfaces.js';
 import * as utils from '../utils.js';
 
@@ -114,8 +114,8 @@ export class AsmParser extends AsmRegex implements IAsmParser {
         this.indentedLabelDef = /^\s*([$.A-Z_a-z][\w$.]*):/;
         this.assignmentDef = /^\s*([$.A-Z_a-z][\w$.]*)\s*=\s*(.*)/;
         this.directive = /^\s*\..*$/;
-        // These four regexes when phrased as /\s*#APP.*/ etc exhibit costly polynomial backtracking
-        // Instead use ^$ and test with regex.test(line.trim()), more robust anyway
+        // These four regexes when phrased as /\s*#APP.*/ etc exhibit costly polynomial backtracking. Instead use ^$ and
+        // test with regex.test(line.trim()), more robust anyway
         this.startAppBlock = /^#APP.*$/;
         this.endAppBlock = /^#NO_APP.*$/;
         this.startAsmNesting = /^# Begin ASM.*$/;
@@ -144,8 +144,8 @@ export class AsmParser extends AsmRegex implements IAsmParser {
             this.lineRe = /^(\/[^:]+):(?<line>\d+).*/;
         }
 
-        // labelRe is made very greedy as it's also used with demangled objdump
-        // output (eg. it can have c++ template with <>).
+        // labelRe is made very greedy as it's also used with demangled objdump output (eg. it can have c++ template
+        // with <>).
         this.labelRe = /^([\da-f]+)\s+<(.+)>:$/;
         this.destRe = /\s([\da-f]+)\s+<([^+>]+)(\+0x[\da-f]+)?>$/;
         this.commentRe = /[#;]/;
@@ -167,11 +167,11 @@ export class AsmParser extends AsmRegex implements IAsmParser {
         this.blockComments = /^[\t ]*\/\*(\*(?!\/)|[^*])*\*\/\s*/gm;
     }
 
-    checkVLIWpacket(line: string, inVLIWpacket: boolean) {
+    checkVLIWpacket(_line: string, inVLIWpacket: boolean) {
         return inVLIWpacket;
     }
 
-    hasOpcode(line: string, inNvccCode: boolean = false, inVLIWpacket: boolean = false) {
+    hasOpcode(line: string, inNvccCode: boolean = false, _inVLIWpacket: boolean = false) {
         // Remove any leading label definition...
         const match = line.match(this.labelDef);
         if (match) {
@@ -184,19 +184,25 @@ export class AsmParser extends AsmRegex implements IAsmParser {
         // Detect assignment, that's not an opcode...
         if (this.assignmentDef.test(line)) return false;
         if (inNvccCode) {
-            return !!this.hasNvccOpcodeRe.test(line);
+            return this.hasNvccOpcodeRe.test(line);
         }
-        return !!this.hasOpcodeRe.test(line);
+        return this.hasOpcodeRe.test(line);
     }
 
     labelFindFor(asmLines: string[]) {
-        const isMips = _.any(asmLines, line => !!this.mipsLabelDefinition.test(line));
+        const isMips = _.any(asmLines, line => this.mipsLabelDefinition.test(line));
         return isMips ? this.labelFindMips : this.labelFindNonMips;
     }
 
-    findUsedLabels(asmLines: string[], filterDirectives?: boolean) {
-        const labelsUsed: Record<string, boolean> = {};
-        const weakUsages: Record<string, Array<string>> = {};
+    findUsedLabels(asmLines: string[], filterDirectives?: boolean): Set<string> {
+        const labelsUsed: Set<string> = new Set();
+        const weakUsages: Map<string, Set<string>> = new Map();
+
+        function markWeak(fromLabel: string, toLabel: string) {
+            if (!weakUsages.has(fromLabel)) weakUsages.set(fromLabel, new Set());
+            unwrap(weakUsages.get(fromLabel)).add(toLabel);
+        }
+
         const labelFind = this.labelFindFor(asmLines);
         // The current label set is the set of labels all pointing at the current code, so:
         // foo:
@@ -213,17 +219,15 @@ export class AsmParser extends AsmRegex implements IAsmParser {
         let inVLIWpacket = false;
         let definingAlias: string | undefined;
 
-        // Scan through looking for definite label usages (ones used by opcodes),
-        // and ones that are weakly used: that is, their use is conditional on another label.
-        // For example:
+        // Scan through looking for definite label usages (ones used by opcodes), and ones that are weakly used: that
+        // is, their use is conditional on another label. For example:
         // .foo: .string "moo"
         // .baz: .quad .foo
         //       mov eax, .baz
         // In this case, the '.baz' is used by an opcode, and so is strongly used.
         // The '.foo' is weakly used by .baz.
-        // Also, if we have random data definitions within a block of a function (between
-        // cfi_startproc and cfi_endproc), we assume they are strong usages. This covers things
-        // like jump tables embedded in ARM code.
+        // Also, if we have random data definitions within a block of a function (between cfi_startproc and
+        // cfi_endproc), we assume they are strong usages. This covers things like jump tables embedded in ARM code.
         // See https://github.com/compiler-explorer/compiler-explorer/issues/2788
         for (let line of asmLines) {
             if (this.startAppBlock.test(line.trim()) || this.startAsmNesting.test(line.trim())) {
@@ -249,8 +253,7 @@ export class AsmParser extends AsmRegex implements IAsmParser {
                 inLabelGroup = true;
                 if (definingAlias) {
                     // If we're defining an alias, then any labels in this group are weakly used by the alias.
-                    if (!weakUsages[definingAlias]) weakUsages[definingAlias] = [];
-                    weakUsages[definingAlias].push(match[1]);
+                    markWeak(definingAlias, match[1]);
                 }
             } else {
                 if (inLabelGroup) {
@@ -262,9 +265,7 @@ export class AsmParser extends AsmRegex implements IAsmParser {
             match = line.match(this.definesGlobal);
             if (!match) match = line.match(this.definesWeak);
             if (!match) match = line.match(this.cudaBeginDef);
-            if (match) {
-                labelsUsed[match[1]] = true;
-            }
+            if (match) labelsUsed.add(match[1]);
 
             const definesAlias = line.match(this.definesAlias);
             if (definesAlias) {
@@ -280,46 +281,37 @@ export class AsmParser extends AsmRegex implements IAsmParser {
 
             if (!filterDirectives || this.hasOpcode(line, inNvccCode, inVLIWpacket) || definesFunction) {
                 // Only count a label as used if it's used by an opcode, or else we're not filtering directives.
-                for (const label of match) labelsUsed[label] = true;
+                for (const label of match) labelsUsed.add(label);
             } else {
                 // If we have a current label, then any subsequent opcode or data definition's labels are referred to
                 // weakly by that label.
-                const isDataDefinition = !!this.dataDefn.test(line);
+                const isDataDefinition = this.dataDefn.test(line);
                 const isOpcode = this.hasOpcode(line, inNvccCode, inVLIWpacket);
                 if (isDataDefinition || isOpcode) {
                     if (inFunction && isDataDefinition) {
                         // Data definitions in the middle of code should be treated as if they were used strongly.
-                        for (const label of match) labelsUsed[label] = true;
+                        for (const label of match) labelsUsed.add(label);
                     } else {
                         for (const currentLabel of currentLabelSet) {
-                            if (!weakUsages[currentLabel]) weakUsages[currentLabel] = [];
-                            for (const label of match) weakUsages[currentLabel].push(label);
+                            for (const label of match) markWeak(currentLabel, label);
                         }
                     }
                 }
             }
         }
 
-        // Now follow the chains of used labels, marking any weak references they refer
-        // to as also used. We iteratively do this until either no new labels are found,
-        // or we hit a limit (only here to prevent a pathological case from hanging).
-        function markUsed(label: string) {
-            labelsUsed[label] = true;
-        }
-
-        const MaxLabelIterations = 10;
-        for (let iter = 0; iter < MaxLabelIterations; ++iter) {
-            const toAdd: string[] = [];
-            _.each(labelsUsed, (t, label) => {
-                // jshint ignore:line
-                _.each(weakUsages[label], (nowused: string) => {
-                    if (labelsUsed[nowused]) return;
-                    toAdd.push(nowused);
-                });
-            });
-            if (!toAdd) break;
-            for (const label of toAdd) markUsed(label);
-        }
+        // Now follow the chains of used labels, marking any weak references they refer to as also used. We recursively
+        // follow the newly-strong references along the path until we hit something that's already marked as used.
+        const recurseMarkUsed = (label: string) => {
+            labelsUsed.add(label);
+            const usages = weakUsages.get(label);
+            if (!usages) return;
+            for (const nowUsed of usages) {
+                if (!labelsUsed.has(nowUsed)) recurseMarkUsed(nowUsed);
+            }
+        };
+        // Iterate over a copy of the initial used labels, as the set will be modified during iteration.
+        for (const label of new Set(labelsUsed)) recurseMarkUsed(label);
         return labelsUsed;
     }
 
@@ -414,7 +406,7 @@ export class AsmParser extends AsmRegex implements IAsmParser {
                     context.source = {
                         file: file,
                         line: sourceLine,
-                        mainsource: !!this.stdInLooking.test(file),
+                        mainsource: this.stdInLooking.test(file),
                     };
                 } else {
                     context.source = {
@@ -447,7 +439,7 @@ export class AsmParser extends AsmRegex implements IAsmParser {
                         context.source = {
                             file: file,
                             line: sourceLine,
-                            mainsource: !!this.stdInLooking.test(file),
+                            mainsource: this.stdInLooking.test(file),
                         };
                     } else {
                         context.source = {
@@ -491,7 +483,7 @@ export class AsmParser extends AsmRegex implements IAsmParser {
                 context.source = {
                     file: file,
                     line: sourceLine,
-                    mainsource: !!this.stdInLooking.test(file),
+                    mainsource: this.stdInLooking.test(file),
                 };
             } else {
                 context.source = {
@@ -636,11 +628,11 @@ export class AsmParser extends AsmRegex implements IAsmParser {
             if (match) {
                 // It's a label definition.
 
-                // g-as shows local labels as eg: "1:  call  mcount". We characterize such a label as
-                // "the label-matching part doesn't equal the whole line" and treat it as used.
-                // As a special case, consider assignments of the form "symbol = ." to be labels.
+                // g-as shows local labels as eg: "1:  call  mcount". We characterize such a label as "the
+                // label-matching part doesn't equal the whole line" and treat it as used. As a special case, consider
+                // assignments of the form "symbol = ." to be labels.
                 if (
-                    labelsUsed[match[1]] === undefined &&
+                    !labelsUsed.has(match[1]) &&
                     match[0] === line &&
                     (match[2] === undefined || match[2].trim() === '.')
                 ) {
@@ -662,8 +654,8 @@ export class AsmParser extends AsmRegex implements IAsmParser {
             if (inNvccDef) {
                 if (this.cudaEndDef.test(line)) inNvccDef = false;
             } else if (!match && filters.directives) {
-                // Check for directives only if it wasn't a label; the regexp would
-                // otherwise misinterpret labels as directives.
+                // Check for directives only if it wasn't a label; the regexp would otherwise misinterpret labels as
+                // directives.
                 if (this.dataDefn.test(line) && context.prevLabel) {
                     // We're defining data that's being used somewhere.
                 } else {
@@ -783,9 +775,9 @@ export class AsmParser extends AsmRegex implements IAsmParser {
 
             if (!func || !this.isUserFunction(func)) continue;
 
-            // note: normally the source.file will be null if it's code from example.ext
-            //  but with filters.dontMaskFilenames it will be filled with the actual filename
-            //  instead we can test source.mainsource in that situation
+            // note: normally the source.file will be null if it's code from example.ext but with
+            //  filters.dontMaskFilenames it will be filled with the actual filename instead we can test
+            //  source.mainsource in that situation
             const isMainsource = source && (source.file === null || source.mainsource);
             if (filters.libraryCode && !isMainsource) {
                 if (mayRemovePreviousLabel && asm.length > 0) {
diff --git a/static/ansi-to-html.ts b/static/ansi-to-html.ts
index dfad84cbc..5684c99c3 100644
--- a/static/ansi-to-html.ts
+++ b/static/ansi-to-html.ts
@@ -30,7 +30,7 @@
 import _ from 'underscore';
 import {AnsiToHtmlOptions, ColorCodes} from './ansi-to-html.interfaces.js';
 import {assert, unwrap} from './assert.js';
-import {isString} from '../shared/common-utils.js';
+import {isString, escapeHTML} from '../shared/common-utils.js';
 
 const defaults: AnsiToHtmlOptions = {
     fg: '#FFF',
@@ -132,7 +132,11 @@ function generateOutput(stack: string[], token: string, data: string | number, o
     } else if (token === 'rgb') {
         assert(isString(data), "Param 'data' must be a string at this point");
         return handleRgb(stack, data, options);
+    } else if (token === 'url') {
+        assert(isString(data), "Param 'data' must be a string at this point");
+        return handleUrl(stack, data, options);
     }
+
     return '';
 }
 
@@ -197,6 +201,11 @@ function handleDisplay(stack: string[], code: string | number, options: AnsiToHt
     return 'Unknown code';
 }
 
+function handleUrl(stack: string[], data: string, options: AnsiToHtmlOptions): string {
+    const [url, text] = data.split(/\x1b\\|\x07/);
+    return `<a class="diagnostic-url" target="_blank" rel="noreferrer" href=${encodeURI(url)}>${escapeHTML(text)}</a>`;
+}
+
 /**
  * Clear all the styles
  */
@@ -317,7 +326,7 @@ interface Token {
 
 function tokenize(text: string, options: AnsiToHtmlOptions, callback: TokenizeCallback) {
     let ansiMatch = false;
-    const ansiHandler = 3;
+    const ansiHandler = 4;
 
     function remove(): string {
         return '';
@@ -364,8 +373,17 @@ function tokenize(text: string, options: AnsiToHtmlOptions, callback: TokenizeCa
         return '';
     }
 
+    function hyperlink(_m: string, captureGroup: string): string {
+        callback('url', captureGroup);
+        return '';
+    }
+
     /* eslint no-control-regex:0 */
     const tokens: Token[] = [
+        {
+            pattern: /^\x1b]8;;(.*?(\x1b\\|\x07).*?)\x1b]8;;\2/,
+            sub: hyperlink,
+        },
         {
             pattern: /^\x08+/,
             sub: remove,
@@ -456,7 +474,7 @@ function updateStickyStack(
     token: string,
     data: string | number,
 ): StickyStackElement[] {
-    if (token !== 'text') {
+    if (token !== 'text' && token !== 'url') {
         stickyStack = stickyStack.filter(notCategory(categoryForCode(data)));
         stickyStack.push({
             token: token,
diff --git a/static/modes/asm-mode.ts b/static/modes/asm-mode.ts
index 1ddebb0ca..b91401164 100644
--- a/static/modes/asm-mode.ts
+++ b/static/modes/asm-mode.ts
@@ -41,14 +41,20 @@ function definition(): monaco.languages.IMonarchLanguage {
                 [/^<.*>$/, {token: 'annotation'}],
                 // inline comments
                 [/\/\*/, 'comment', '@comment'],
-                // Label definition
-                [/^[.a-zA-Z0-9_$?@].*:/, {token: 'type.identifier'}],
+                // Label definition (anything looking like a label, followed by anything that's not valid in a demangled
+                // identifier, until we get to a colon followed by any whitespace. This is to avoid finding the colon in
+                // a scoped (blah::foo) identifier.
+                [/^[.a-zA-Z0-9_$?@][^#;/]*:(?=\s)/, {token: 'type.identifier'}],
                 // Label definition (quoted)
                 [/^"([^"\\]|\\.)*":/, {token: 'type.identifier'}],
                 // Label definition (ARM style)
                 [/^\s*[|][^|]*[|]/, {token: 'type.identifier'}],
-                // Label definition (CL style)
-                [/^\s*[.a-zA-Z0-9_$|]*\s+(PROC|ENDP|DB|DD)/, {token: 'type.identifier', next: '@rest'}],
+                // Label definition (CL style). This is pretty hideous: we essentially take anything that ends in spaces
+                // followed by a definition (PROC, ENDP etc) and assume it's a label. That means we have to use
+                // backtracking and then a lookahead to ensure we don't consume the definition. As a nod to efficiency
+                // we assume the line has to start with a non-whitespace character before we go all back-tracky.
+                // See https://github.com/compiler-explorer/compiler-explorer/issues/1645 for examples.
+                [/^\S.*?(?=\s+(PROC|ENDP|D[BDWQ]))/, {token: 'type.identifier', next: '@rest'}],
                 // Constant definition
                 [/^[.a-zA-Z0-9_$?@][^=]*=/, {token: 'type.identifier'}],
                 // opcode
@@ -70,6 +76,8 @@ function definition(): monaco.languages.IMonarchLanguage {
                 [/@intelOperators/, 'annotation'],
                 // inline comments
                 [/\/\*/, 'comment', '@comment'],
+                // CL style post-label definition.
+                [/PROC|ENDP|D[BDWQ]/, 'keyword'],
 
                 // brackets
                 [/[{}<>()[\]]/, '@brackets'],
diff --git a/static/panes/output.ts b/static/panes/output.ts
index 053b2fd0f..6b67445f7 100644
--- a/static/panes/output.ts
+++ b/static/panes/output.ts
@@ -285,6 +285,9 @@ export class Output extends Pane<OutputState> {
                     e.preventDefault();
                     return false;
                 })
+                .on('click', '.diagnostic-url', e => {
+                    e.stopPropagation();
+                })
                 .on('mouseover', () => {
                     this.emitEditorLinkLine(lineNum, column, filename, false);
                 })


Test patch:
diff --git a/test/ansi-to-html-tests.ts b/test/ansi-to-html-tests.ts
index b7fa6af89..6ae0132ac 100644
--- a/test/ansi-to-html-tests.ts
+++ b/test/ansi-to-html-tests.ts
@@ -90,4 +90,32 @@ describe('ansi-to-html', () => {
             '<span style="color:#39aaf3">foo<span style="background-color:#646464">bar</span></span>',
         );
     });
+
+    // hyperlinks
+    it('should parse terminal hyperlinks', () => {
+        const filter = new Filter(filterOpts);
+        expect(
+            filter.toHtml(
+                'error[\x1B]8;;https://doc.rust-lang.org/error_codes/E0425.html\x07E0425\x1B]8;;\x07]: cannot find value `x` in this scope',
+            ),
+        ).toEqual(
+            'error[<a class="diagnostic-url" target="_blank" rel="noreferrer" href=https://doc.rust-lang.org/error_codes/E0425.html>E0425</a>]: cannot find value `x` in this scope',
+        );
+        expect(
+            filter.toHtml(
+                't.c:3:1: warning: control reaches end of non-void function [\x1B]8;;https://gcc.gnu.org/onlinedocs/gcc-14.2.0/gcc/Warning-Options.html#index-Wno-return-type\x1B\\-Wreturn-type\x1B]8;;\x1B\\]',
+            ),
+        ).toEqual(
+            't.c:3:1: warning: control reaches end of non-void function [<a class="diagnostic-url" target="_blank" rel="noreferrer" href=https://gcc.gnu.org/onlinedocs/gcc-14.2.0/gcc/Warning-Options.html#index-Wno-return-type>-Wreturn-type</a>]',
+        );
+    });
+
+    it('should properly escape hyperlinks', () => {
+        const filter = new Filter(filterOpts);
+        expect(
+            filter.toHtml('\x1B]8;;https://example.org/</a><b>bold</b><a>link\x07<i>italic</i>\x1B]8;;\x07'),
+        ).toEqual(
+            '<a class="diagnostic-url" target="_blank" rel="noreferrer" href=https://example.org/%3C/a%3E%3Cb%3Ebold%3C/b%3E%3Ca%3Elink>&lt;i&gt;italic&lt;/i&gt;</a>',
+        );
+    });
 });
diff --git a/test/handlers/api/formatting-controller.tests.ts b/test/handlers/api/formatting-controller.tests.ts
index f1aee3328..fa8f3866a 100644
--- a/test/handlers/api/formatting-controller.tests.ts
+++ b/test/handlers/api/formatting-controller.tests.ts
@@ -54,10 +54,19 @@ describe('FormattingController', () => {
         await request(app)
             .post('/api/format/invalid')
             .set('Accept', 'application/json')
+            .set('Content-Type', 'application/json')
             .expect('Content-Type', /json/)
             .expect(422, {exit: 2, answer: "Unknown format tool 'invalid'"});
     });
 
+    it('should reject requests with no content type', async () => {
+        await request(app)
+            .post('/api/format/formatt')
+            .send('{ base: "something", source: "int main() {}" }')
+            .expect('Content-Type', /json/)
+            .expect(400);
+    });
+
     it('should not go through with invalid base styles', async () => {
         await request(app)
             .post('/api/format/formatt')
@@ -66,6 +75,7 @@ describe('FormattingController', () => {
                 source: 'i am source',
             })
             .set('Accept', 'application/json')
+            .set('Content-Type', 'application/json')
             .expect(422, {exit: 3, answer: "Style 'bad-base' is not supported"})
             .expect('Content-Type', /json/);
     });
diff --git a/test/odin-tests.ts b/test/odin-tests.ts
index 8daeb3385..88164d1b1 100644
--- a/test/odin-tests.ts
+++ b/test/odin-tests.ts
@@ -46,7 +46,7 @@ const info = {
     lang: languages.odin.id,
 };
 
-describe('GO asm tests', () => {
+describe('Odin source preprocessing tests', () => {
     beforeAll(() => {
         ce = makeCompilationEnvironment({languages});
     });
diff --git a/test/odin/add_require.odin.expected b/test/odin/add_require.odin.expected
index 5020a61df..61f57775f 100644
--- a/test/odin/add_require.odin.expected
+++ b/test/odin/add_require.odin.expected
@@ -13,6 +13,6 @@ test_proc1 :: proc() {
 test_proc2 :: proc() {
 }
 
-main :: proc() {
+@(require) main :: proc() {
     fmt.println("asd")
 }