Skip to content

Rename rapidjson helper functions; remove in situ parsing - #114017

Merged
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson
Jul 8, 2026
Merged

Rename rapidjson helper functions; remove in situ parsing#114017
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson

Conversation

@GrabYourPitchforks

@GrabYourPitchforksGrabYourPitchforks commented Mar 28, 2025

Copy link
Copy Markdown
Member

Since rapidjson is a disallowed deserializer within Microsoft, signing off on .NET 10 will require us to attest that every call site into rapidjson processes only fully trusted input.

This is straightforward enough for a point-in-time assessment, but it does represent ongoing cost, and we don't want to risk introducing new call sites into this logic where we can't guarantee that the input is trustworthy. To facilitate this, I recommend renaming our rapidjson wrapping utility methods to parse_fully_trusted_raw_data and parse_fully_trusted_file, which clearly indicate at the invocation site that the caller passes only fully trustworthy data. This should reduce the risk of us violating the trust contract going forward and should simplify future attestations.

Additionally, we received internal bug reports of in situ parsing causing AVs when parsing improperly formatted JSON payloads. rapidjson's in situ parser requires a mutable null-terminated string; and since we use mmap files, we can't guarantee the presence of a null terminator. This means that if the file length happens to exactly match the page size of the underlying OS, and if the underlying JSON blob has an error (like missing the closing bracket), rapidjson will try to read into unmapped memory and crash the process rather than report an actionable error.

This is resolved by passing an explicit length to the parse routine. However, since ParseInsitu doesn't provide an overload that takes an explicit length, we should fall back to normal parsing.

Note to reviewers: If we really do want to enable in-situ parsing, we could always create our own GenericInsituStringStream-like type which takes a length and call ParseStream, passing kParseInsituFlag as a template arg. We can't use MemoryStream directly since it's read-only.

(Jeff, this is what I pinged you about via IM.)

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.

Files not reviewed (7)
  • src/native/corehost/comhost/clsidmap.cpp: Language not supported
  • src/native/corehost/fxr/sdk_resolver.cpp: Language not supported
  • src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp: Language not supported
  • src/native/corehost/hostpolicy/deps_format.cpp: Language not supported
  • src/native/corehost/json_parser.cpp: Language not supported
  • src/native/corehost/json_parser.h: Language not supported
  • src/native/corehost/runtime_config.cpp: Language not supported

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @vitek-karas, @agocke, @VSadov
See info in area-owners.md if you want to be subscribed.

@GrabYourPitchforks

Copy link
Copy Markdown
MemberAuthor

Rebased on latest main to resolve merge conflicts.

CopilotAI review requested due to automatic review settings March 24, 2026 19:01
@GrabYourPitchforksGrabYourPitchforks changed the title Rename rapidjson helper functionsRename rapidjson helper functions; remove in situ parsingMar 24, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

src/native/corehost/json_parser.cpp:114

  • In parse_fully_trusted_file, m_data = (char*)pal::mmap_read(...) casts away const from a read-only mapping. Even though current parsing is non-mutating, this makes it easy to accidentally introduce writes later (which would fault on Unix/Windows). Prefer keeping the mapping pointer const throughout and only using mutable mappings when truly required.
 if (m_data == nullptr)
{
m_data = (char*)pal::mmap_read(path, &m_size);
if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;
}

src/native/corehost/json_parser.cpp:113

  • The error text "Cannot use file stream for [...]" is misleading here since the code path is attempting a memory-map (mmap_read), not a file stream. Consider updating the message to reflect the actual operation (mapping the file) to make diagnostics clearer.
 if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;

src/native/corehost/json_parser.cpp:36

  • get_line_column_from_offset can read past the end of the buffer: when i == size - 1 (possible when offset == size), the data[i + 1] access in the CRLF check is out-of-bounds. Add a bounds check (e.g., ensure i + 1 < size) before reading data[i + 1] so malformed/edge inputs don’t trigger an OOB read while formatting parse errors.
void get_line_column_from_offset(const char* data, size_t size, size_t offset, int *line, int *column)
{
assert(offset <= size);
*line = *column = 1;
for (size_t i = 0; i < offset; i++)
{
(*column)++;
if (data[i] == '\n')
{
(*line)++;
*column = 1;
}
else if (data[i] == '\r' && data[i + 1] == '\n')
{

src/native/corehost/json_parser.h:52

  • pal::mmap_read returns a const void* (read-only mapping), but m_data is a mutable char* and the code casts away const when assigning the mapping. Since parsing no longer uses in-situ mutation, consider making m_data a const char* and updating parse_fully_trusted_raw_data to take const char* to avoid misleading mutability and accidental writes to a read-only mapping.
 bool parse_fully_trusted_raw_data(char* data, size_t size, const pal::string_t& context);
bool parse_fully_trusted_file(const pal::string_t& path);
json_parser_t()
: m_data(nullptr)
, m_bundle_location(nullptr) {}
~json_parser_t();
private:
char* m_data; // The memory mapped bytes of the file
size_t m_size; // Size of the mapped memory

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/corehost/json_parser.cpp:40

  • In get_line_column_from_offset, the CRLF detection now checks (i + 1) < offset instead of guarding against the end of the buffer. This can under-count newlines when the error offset points at the \n of a CRLF pair, and it’s not necessary for bounds safety (the real bound is size). Consider changing the condition to guard with size so CRLF is recognized when present while still avoiding an out-of-bounds read.
 else if (data[i] == '\r' && (i + 1) < offset && data[i + 1] == '\n')
{
(*line)++;
*column = 1;
i++; // Discard carriage return

Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@pavelsavara

pavelsavara commented Mar 25, 2026

Copy link
Copy Markdown
Member

I will comment from browser/wasm perspective.

  • I believe that main use-case for json parser in coreCLR codebase is to parse runtimeconfig.json
  • in browser the host is statically linked with the runtime, the the part that chooses runtime version doesn't apply.
  • so we only need to know values of configProperties and that's is (string,string)[] list of pair of strings.
  • I think it's like that for all "mobile" targets.
  • in browserhost for CoreCLR we use browser json parser to parse much larger manifest, and configProperties are part of that. The JS side will transform that directly into appctx_keys, appctx_values C UTF8 char**
  • in mono for browser it's also like that

for(const[key,value]ofruntimeConfigProperties.entries()){
constkeyPtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(key);
constvaluePtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(value);
_ems_.dotnetApi.setHeapU32((appctx_keysasany)+(propertyCount*sizeOfPtr),keyPtr);
_ems_.dotnetApi.setHeapU32((appctx_valuesasany)+(propertyCount*sizeOfPtr),valuePtr);
propertyCount++;
buffers.push(keyPtrasany);
buffers.push(valuePtrasany);
}

construntimeConfigProperties=newMap<string,string>();
if(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties){
for(const[key,value]ofObject.entries(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties)){
runtimeConfigProperties.set(key,""+value);
}
}
runtimeConfigProperties.set("APP_CONTEXT_BASE_DIRECTORY","/");
runtimeConfigProperties.set("RUNTIME_IDENTIFIER","browser-wasm");
constpropertyCount=runtimeConfigProperties.size;
constbuffers:VoidPtr[]=[];
constappctx_keys=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;
constappctx_values=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;

In the past we avoided need for json parser in the VM, by converting into very simple binary file at compile time.
For browser I got rid of that in #115113

But I still see the same idea on apple/android

if(BundledRuntimeConfig?.ItemSpec!=null)
{
dataSymbol=BundledRuntimeConfig.GetMetadata("DataSymbol");
if(string.IsNullOrEmpty(dataSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataSymbol' metadata.");
}
dataLenSymbol=BundledRuntimeConfig.GetMetadata("DataLenSymbol");
if(string.IsNullOrEmpty(dataLenSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataLenSymbol' metadata.");
}
externBundledResourcesSymbols.AppendLine();
externBundledResourcesSymbols.AppendLine($"extern uint8_t {dataSymbol}[];");
externBundledResourcesSymbols.AppendLine($"extern const uint32_t {dataLenSymbol};");
}

In the future, we may still need this compile time trick for WASI, but just for the configProperties part.

So my angle is, let's delete it, it makes coreCLR binary larger download for the browser.
(I realize normal OS story is different)

@jkotas

Copy link
Copy Markdown
Member

it makes coreCLR binary larger download for the browser.

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

@pavelsavara

Copy link
Copy Markdown
Member

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

host and vm are the same binary for wasm, but I stand corrected, we are NOT building/linking it in the browser.

if(NOT CLR_CMAKE_TARGET_BROWSER)
add_library(fxr_resolverINTERFACE)
target_sources(fxr_resolverINTERFACEfxr_resolver.cpp)
target_include_directories(fxr_resolverINTERFACEfxr)
add_compile_definitions(RAPIDJSON_HAS_CXX17)
if ((NOTDEFINED CLR_CMAKE_USE_SYSTEM_RAPIDJSON) OR (NOT CLR_CMAKE_USE_SYSTEM_RAPIDJSON))
include_directories(${CLR_SRC_NATIVE_DIR}/external/)
endif()

I don't know about other single-file hosts.

@GrabYourPitchforks

GrabYourPitchforks commented Mar 27, 2026

Copy link
Copy Markdown
MemberAuthor

Elinor requested offline that I run the startup perf benchmarks. Should get to that next week.

Edit: No measurable change. 60 ~ 62 ms on my machine to launch emptycsconsoletemplate, regardless of whether the runtime comes from main or from this PR's branch. I also confirmed the perf scaffolding drops a runtimeconfig.json file alongside the executable, which I tweaked to target net11:

{
"runtimeOptions": {
"tfm": "net11.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "11.0.0"
},
"configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

CopilotAI review requested due to automatic review settings July 7, 2026 23:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/corehost/json_parser.h
Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@GrabYourPitchforks
GrabYourPitchforks merged commit 0f9f04b into dotnet:mainJul 8, 2026
172 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
@am11am11 mentioned this pull request Jul 24, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@GrabYourPitchforks@pavelsavara@jkotas@elinor-fung@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Rename rapidjson helper functions; remove in situ parsing by GrabYourPitchforks · Pull Request #114017 · dotnet/runtime · GitHub
Skip to content

Rename rapidjson helper functions; remove in situ parsing - #114017

Merged
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson
Jul 8, 2026
Merged

Rename rapidjson helper functions; remove in situ parsing#114017
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson

Conversation

@GrabYourPitchforks

@GrabYourPitchforksGrabYourPitchforks commented Mar 28, 2025

Copy link
Copy Markdown
Member

Since rapidjson is a disallowed deserializer within Microsoft, signing off on .NET 10 will require us to attest that every call site into rapidjson processes only fully trusted input.

This is straightforward enough for a point-in-time assessment, but it does represent ongoing cost, and we don't want to risk introducing new call sites into this logic where we can't guarantee that the input is trustworthy. To facilitate this, I recommend renaming our rapidjson wrapping utility methods to parse_fully_trusted_raw_data and parse_fully_trusted_file, which clearly indicate at the invocation site that the caller passes only fully trustworthy data. This should reduce the risk of us violating the trust contract going forward and should simplify future attestations.

Additionally, we received internal bug reports of in situ parsing causing AVs when parsing improperly formatted JSON payloads. rapidjson's in situ parser requires a mutable null-terminated string; and since we use mmap files, we can't guarantee the presence of a null terminator. This means that if the file length happens to exactly match the page size of the underlying OS, and if the underlying JSON blob has an error (like missing the closing bracket), rapidjson will try to read into unmapped memory and crash the process rather than report an actionable error.

This is resolved by passing an explicit length to the parse routine. However, since ParseInsitu doesn't provide an overload that takes an explicit length, we should fall back to normal parsing.

Note to reviewers: If we really do want to enable in-situ parsing, we could always create our own GenericInsituStringStream-like type which takes a length and call ParseStream, passing kParseInsituFlag as a template arg. We can't use MemoryStream directly since it's read-only.

(Jeff, this is what I pinged you about via IM.)

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.

Files not reviewed (7)
  • src/native/corehost/comhost/clsidmap.cpp: Language not supported
  • src/native/corehost/fxr/sdk_resolver.cpp: Language not supported
  • src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp: Language not supported
  • src/native/corehost/hostpolicy/deps_format.cpp: Language not supported
  • src/native/corehost/json_parser.cpp: Language not supported
  • src/native/corehost/json_parser.h: Language not supported
  • src/native/corehost/runtime_config.cpp: Language not supported

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @vitek-karas, @agocke, @VSadov
See info in area-owners.md if you want to be subscribed.

@GrabYourPitchforks

Copy link
Copy Markdown
MemberAuthor

Rebased on latest main to resolve merge conflicts.

CopilotAI review requested due to automatic review settings March 24, 2026 19:01
@GrabYourPitchforksGrabYourPitchforks changed the title Rename rapidjson helper functionsRename rapidjson helper functions; remove in situ parsingMar 24, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

src/native/corehost/json_parser.cpp:114

  • In parse_fully_trusted_file, m_data = (char*)pal::mmap_read(...) casts away const from a read-only mapping. Even though current parsing is non-mutating, this makes it easy to accidentally introduce writes later (which would fault on Unix/Windows). Prefer keeping the mapping pointer const throughout and only using mutable mappings when truly required.
 if (m_data == nullptr)
{
m_data = (char*)pal::mmap_read(path, &m_size);
if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;
}

src/native/corehost/json_parser.cpp:113

  • The error text "Cannot use file stream for [...]" is misleading here since the code path is attempting a memory-map (mmap_read), not a file stream. Consider updating the message to reflect the actual operation (mapping the file) to make diagnostics clearer.
 if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;

src/native/corehost/json_parser.cpp:36

  • get_line_column_from_offset can read past the end of the buffer: when i == size - 1 (possible when offset == size), the data[i + 1] access in the CRLF check is out-of-bounds. Add a bounds check (e.g., ensure i + 1 < size) before reading data[i + 1] so malformed/edge inputs don’t trigger an OOB read while formatting parse errors.
void get_line_column_from_offset(const char* data, size_t size, size_t offset, int *line, int *column)
{
assert(offset <= size);
*line = *column = 1;
for (size_t i = 0; i < offset; i++)
{
(*column)++;
if (data[i] == '\n')
{
(*line)++;
*column = 1;
}
else if (data[i] == '\r' && data[i + 1] == '\n')
{

src/native/corehost/json_parser.h:52

  • pal::mmap_read returns a const void* (read-only mapping), but m_data is a mutable char* and the code casts away const when assigning the mapping. Since parsing no longer uses in-situ mutation, consider making m_data a const char* and updating parse_fully_trusted_raw_data to take const char* to avoid misleading mutability and accidental writes to a read-only mapping.
 bool parse_fully_trusted_raw_data(char* data, size_t size, const pal::string_t& context);
bool parse_fully_trusted_file(const pal::string_t& path);
json_parser_t()
: m_data(nullptr)
, m_bundle_location(nullptr) {}
~json_parser_t();
private:
char* m_data; // The memory mapped bytes of the file
size_t m_size; // Size of the mapped memory

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/corehost/json_parser.cpp:40

  • In get_line_column_from_offset, the CRLF detection now checks (i + 1) < offset instead of guarding against the end of the buffer. This can under-count newlines when the error offset points at the \n of a CRLF pair, and it’s not necessary for bounds safety (the real bound is size). Consider changing the condition to guard with size so CRLF is recognized when present while still avoiding an out-of-bounds read.
 else if (data[i] == '\r' && (i + 1) < offset && data[i + 1] == '\n')
{
(*line)++;
*column = 1;
i++; // Discard carriage return

Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@pavelsavara

pavelsavara commented Mar 25, 2026

Copy link
Copy Markdown
Member

I will comment from browser/wasm perspective.

  • I believe that main use-case for json parser in coreCLR codebase is to parse runtimeconfig.json
  • in browser the host is statically linked with the runtime, the the part that chooses runtime version doesn't apply.
  • so we only need to know values of configProperties and that's is (string,string)[] list of pair of strings.
  • I think it's like that for all "mobile" targets.
  • in browserhost for CoreCLR we use browser json parser to parse much larger manifest, and configProperties are part of that. The JS side will transform that directly into appctx_keys, appctx_values C UTF8 char**
  • in mono for browser it's also like that

for(const[key,value]ofruntimeConfigProperties.entries()){
constkeyPtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(key);
constvaluePtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(value);
_ems_.dotnetApi.setHeapU32((appctx_keysasany)+(propertyCount*sizeOfPtr),keyPtr);
_ems_.dotnetApi.setHeapU32((appctx_valuesasany)+(propertyCount*sizeOfPtr),valuePtr);
propertyCount++;
buffers.push(keyPtrasany);
buffers.push(valuePtrasany);
}

construntimeConfigProperties=newMap<string,string>();
if(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties){
for(const[key,value]ofObject.entries(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties)){
runtimeConfigProperties.set(key,""+value);
}
}
runtimeConfigProperties.set("APP_CONTEXT_BASE_DIRECTORY","/");
runtimeConfigProperties.set("RUNTIME_IDENTIFIER","browser-wasm");
constpropertyCount=runtimeConfigProperties.size;
constbuffers:VoidPtr[]=[];
constappctx_keys=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;
constappctx_values=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;

In the past we avoided need for json parser in the VM, by converting into very simple binary file at compile time.
For browser I got rid of that in #115113

But I still see the same idea on apple/android

if(BundledRuntimeConfig?.ItemSpec!=null)
{
dataSymbol=BundledRuntimeConfig.GetMetadata("DataSymbol");
if(string.IsNullOrEmpty(dataSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataSymbol' metadata.");
}
dataLenSymbol=BundledRuntimeConfig.GetMetadata("DataLenSymbol");
if(string.IsNullOrEmpty(dataLenSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataLenSymbol' metadata.");
}
externBundledResourcesSymbols.AppendLine();
externBundledResourcesSymbols.AppendLine($"extern uint8_t {dataSymbol}[];");
externBundledResourcesSymbols.AppendLine($"extern const uint32_t {dataLenSymbol};");
}

In the future, we may still need this compile time trick for WASI, but just for the configProperties part.

So my angle is, let's delete it, it makes coreCLR binary larger download for the browser.
(I realize normal OS story is different)

@jkotas

Copy link
Copy Markdown
Member

it makes coreCLR binary larger download for the browser.

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

@pavelsavara

Copy link
Copy Markdown
Member

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

host and vm are the same binary for wasm, but I stand corrected, we are NOT building/linking it in the browser.

if(NOT CLR_CMAKE_TARGET_BROWSER)
add_library(fxr_resolverINTERFACE)
target_sources(fxr_resolverINTERFACEfxr_resolver.cpp)
target_include_directories(fxr_resolverINTERFACEfxr)
add_compile_definitions(RAPIDJSON_HAS_CXX17)
if ((NOTDEFINED CLR_CMAKE_USE_SYSTEM_RAPIDJSON) OR (NOT CLR_CMAKE_USE_SYSTEM_RAPIDJSON))
include_directories(${CLR_SRC_NATIVE_DIR}/external/)
endif()

I don't know about other single-file hosts.

@GrabYourPitchforks

GrabYourPitchforks commented Mar 27, 2026

Copy link
Copy Markdown
MemberAuthor

Elinor requested offline that I run the startup perf benchmarks. Should get to that next week.

Edit: No measurable change. 60 ~ 62 ms on my machine to launch emptycsconsoletemplate, regardless of whether the runtime comes from main or from this PR's branch. I also confirmed the perf scaffolding drops a runtimeconfig.json file alongside the executable, which I tweaked to target net11:

{
"runtimeOptions": {
"tfm": "net11.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "11.0.0"
},
"configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

CopilotAI review requested due to automatic review settings July 7, 2026 23:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/corehost/json_parser.h
Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@GrabYourPitchforks
GrabYourPitchforks merged commit 0f9f04b into dotnet:mainJul 8, 2026
172 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
@am11am11 mentioned this pull request Jul 24, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@GrabYourPitchforks@pavelsavara@jkotas@elinor-fung@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Rename rapidjson helper functions; remove in situ parsing by GrabYourPitchforks · Pull Request #114017 · dotnet/runtime · GitHub
Skip to content

Rename rapidjson helper functions; remove in situ parsing - #114017

Merged
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson
Jul 8, 2026
Merged

Rename rapidjson helper functions; remove in situ parsing#114017
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson

Conversation

@GrabYourPitchforks

@GrabYourPitchforksGrabYourPitchforks commented Mar 28, 2025

Copy link
Copy Markdown
Member

Since rapidjson is a disallowed deserializer within Microsoft, signing off on .NET 10 will require us to attest that every call site into rapidjson processes only fully trusted input.

This is straightforward enough for a point-in-time assessment, but it does represent ongoing cost, and we don't want to risk introducing new call sites into this logic where we can't guarantee that the input is trustworthy. To facilitate this, I recommend renaming our rapidjson wrapping utility methods to parse_fully_trusted_raw_data and parse_fully_trusted_file, which clearly indicate at the invocation site that the caller passes only fully trustworthy data. This should reduce the risk of us violating the trust contract going forward and should simplify future attestations.

Additionally, we received internal bug reports of in situ parsing causing AVs when parsing improperly formatted JSON payloads. rapidjson's in situ parser requires a mutable null-terminated string; and since we use mmap files, we can't guarantee the presence of a null terminator. This means that if the file length happens to exactly match the page size of the underlying OS, and if the underlying JSON blob has an error (like missing the closing bracket), rapidjson will try to read into unmapped memory and crash the process rather than report an actionable error.

This is resolved by passing an explicit length to the parse routine. However, since ParseInsitu doesn't provide an overload that takes an explicit length, we should fall back to normal parsing.

Note to reviewers: If we really do want to enable in-situ parsing, we could always create our own GenericInsituStringStream-like type which takes a length and call ParseStream, passing kParseInsituFlag as a template arg. We can't use MemoryStream directly since it's read-only.

(Jeff, this is what I pinged you about via IM.)

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.

Files not reviewed (7)
  • src/native/corehost/comhost/clsidmap.cpp: Language not supported
  • src/native/corehost/fxr/sdk_resolver.cpp: Language not supported
  • src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp: Language not supported
  • src/native/corehost/hostpolicy/deps_format.cpp: Language not supported
  • src/native/corehost/json_parser.cpp: Language not supported
  • src/native/corehost/json_parser.h: Language not supported
  • src/native/corehost/runtime_config.cpp: Language not supported

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @vitek-karas, @agocke, @VSadov
See info in area-owners.md if you want to be subscribed.

@GrabYourPitchforks

Copy link
Copy Markdown
MemberAuthor

Rebased on latest main to resolve merge conflicts.

CopilotAI review requested due to automatic review settings March 24, 2026 19:01
@GrabYourPitchforksGrabYourPitchforks changed the title Rename rapidjson helper functionsRename rapidjson helper functions; remove in situ parsingMar 24, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

src/native/corehost/json_parser.cpp:114

  • In parse_fully_trusted_file, m_data = (char*)pal::mmap_read(...) casts away const from a read-only mapping. Even though current parsing is non-mutating, this makes it easy to accidentally introduce writes later (which would fault on Unix/Windows). Prefer keeping the mapping pointer const throughout and only using mutable mappings when truly required.
 if (m_data == nullptr)
{
m_data = (char*)pal::mmap_read(path, &m_size);
if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;
}

src/native/corehost/json_parser.cpp:113

  • The error text "Cannot use file stream for [...]" is misleading here since the code path is attempting a memory-map (mmap_read), not a file stream. Consider updating the message to reflect the actual operation (mapping the file) to make diagnostics clearer.
 if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;

src/native/corehost/json_parser.cpp:36

  • get_line_column_from_offset can read past the end of the buffer: when i == size - 1 (possible when offset == size), the data[i + 1] access in the CRLF check is out-of-bounds. Add a bounds check (e.g., ensure i + 1 < size) before reading data[i + 1] so malformed/edge inputs don’t trigger an OOB read while formatting parse errors.
void get_line_column_from_offset(const char* data, size_t size, size_t offset, int *line, int *column)
{
assert(offset <= size);
*line = *column = 1;
for (size_t i = 0; i < offset; i++)
{
(*column)++;
if (data[i] == '\n')
{
(*line)++;
*column = 1;
}
else if (data[i] == '\r' && data[i + 1] == '\n')
{

src/native/corehost/json_parser.h:52

  • pal::mmap_read returns a const void* (read-only mapping), but m_data is a mutable char* and the code casts away const when assigning the mapping. Since parsing no longer uses in-situ mutation, consider making m_data a const char* and updating parse_fully_trusted_raw_data to take const char* to avoid misleading mutability and accidental writes to a read-only mapping.
 bool parse_fully_trusted_raw_data(char* data, size_t size, const pal::string_t& context);
bool parse_fully_trusted_file(const pal::string_t& path);
json_parser_t()
: m_data(nullptr)
, m_bundle_location(nullptr) {}
~json_parser_t();
private:
char* m_data; // The memory mapped bytes of the file
size_t m_size; // Size of the mapped memory

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/corehost/json_parser.cpp:40

  • In get_line_column_from_offset, the CRLF detection now checks (i + 1) < offset instead of guarding against the end of the buffer. This can under-count newlines when the error offset points at the \n of a CRLF pair, and it’s not necessary for bounds safety (the real bound is size). Consider changing the condition to guard with size so CRLF is recognized when present while still avoiding an out-of-bounds read.
 else if (data[i] == '\r' && (i + 1) < offset && data[i + 1] == '\n')
{
(*line)++;
*column = 1;
i++; // Discard carriage return

Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@pavelsavara

pavelsavara commented Mar 25, 2026

Copy link
Copy Markdown
Member

I will comment from browser/wasm perspective.

  • I believe that main use-case for json parser in coreCLR codebase is to parse runtimeconfig.json
  • in browser the host is statically linked with the runtime, the the part that chooses runtime version doesn't apply.
  • so we only need to know values of configProperties and that's is (string,string)[] list of pair of strings.
  • I think it's like that for all "mobile" targets.
  • in browserhost for CoreCLR we use browser json parser to parse much larger manifest, and configProperties are part of that. The JS side will transform that directly into appctx_keys, appctx_values C UTF8 char**
  • in mono for browser it's also like that

for(const[key,value]ofruntimeConfigProperties.entries()){
constkeyPtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(key);
constvaluePtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(value);
_ems_.dotnetApi.setHeapU32((appctx_keysasany)+(propertyCount*sizeOfPtr),keyPtr);
_ems_.dotnetApi.setHeapU32((appctx_valuesasany)+(propertyCount*sizeOfPtr),valuePtr);
propertyCount++;
buffers.push(keyPtrasany);
buffers.push(valuePtrasany);
}

construntimeConfigProperties=newMap<string,string>();
if(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties){
for(const[key,value]ofObject.entries(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties)){
runtimeConfigProperties.set(key,""+value);
}
}
runtimeConfigProperties.set("APP_CONTEXT_BASE_DIRECTORY","/");
runtimeConfigProperties.set("RUNTIME_IDENTIFIER","browser-wasm");
constpropertyCount=runtimeConfigProperties.size;
constbuffers:VoidPtr[]=[];
constappctx_keys=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;
constappctx_values=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;

In the past we avoided need for json parser in the VM, by converting into very simple binary file at compile time.
For browser I got rid of that in #115113

But I still see the same idea on apple/android

if(BundledRuntimeConfig?.ItemSpec!=null)
{
dataSymbol=BundledRuntimeConfig.GetMetadata("DataSymbol");
if(string.IsNullOrEmpty(dataSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataSymbol' metadata.");
}
dataLenSymbol=BundledRuntimeConfig.GetMetadata("DataLenSymbol");
if(string.IsNullOrEmpty(dataLenSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataLenSymbol' metadata.");
}
externBundledResourcesSymbols.AppendLine();
externBundledResourcesSymbols.AppendLine($"extern uint8_t {dataSymbol}[];");
externBundledResourcesSymbols.AppendLine($"extern const uint32_t {dataLenSymbol};");
}

In the future, we may still need this compile time trick for WASI, but just for the configProperties part.

So my angle is, let's delete it, it makes coreCLR binary larger download for the browser.
(I realize normal OS story is different)

@jkotas

Copy link
Copy Markdown
Member

it makes coreCLR binary larger download for the browser.

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

@pavelsavara

Copy link
Copy Markdown
Member

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

host and vm are the same binary for wasm, but I stand corrected, we are NOT building/linking it in the browser.

if(NOT CLR_CMAKE_TARGET_BROWSER)
add_library(fxr_resolverINTERFACE)
target_sources(fxr_resolverINTERFACEfxr_resolver.cpp)
target_include_directories(fxr_resolverINTERFACEfxr)
add_compile_definitions(RAPIDJSON_HAS_CXX17)
if ((NOTDEFINED CLR_CMAKE_USE_SYSTEM_RAPIDJSON) OR (NOT CLR_CMAKE_USE_SYSTEM_RAPIDJSON))
include_directories(${CLR_SRC_NATIVE_DIR}/external/)
endif()

I don't know about other single-file hosts.

@GrabYourPitchforks

GrabYourPitchforks commented Mar 27, 2026

Copy link
Copy Markdown
MemberAuthor

Elinor requested offline that I run the startup perf benchmarks. Should get to that next week.

Edit: No measurable change. 60 ~ 62 ms on my machine to launch emptycsconsoletemplate, regardless of whether the runtime comes from main or from this PR's branch. I also confirmed the perf scaffolding drops a runtimeconfig.json file alongside the executable, which I tweaked to target net11:

{
"runtimeOptions": {
"tfm": "net11.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "11.0.0"
},
"configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

CopilotAI review requested due to automatic review settings July 7, 2026 23:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/corehost/json_parser.h
Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@GrabYourPitchforks
GrabYourPitchforks merged commit 0f9f04b into dotnet:mainJul 8, 2026
172 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
@am11am11 mentioned this pull request Jul 24, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@GrabYourPitchforks@pavelsavara@jkotas@elinor-fung@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Rename rapidjson helper functions; remove in situ parsing by GrabYourPitchforks · Pull Request #114017 · dotnet/runtime · GitHub
Skip to content

Rename rapidjson helper functions; remove in situ parsing - #114017

Merged
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson
Jul 8, 2026
Merged

Rename rapidjson helper functions; remove in situ parsing#114017
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson

Conversation

@GrabYourPitchforks

@GrabYourPitchforksGrabYourPitchforks commented Mar 28, 2025

Copy link
Copy Markdown
Member

Since rapidjson is a disallowed deserializer within Microsoft, signing off on .NET 10 will require us to attest that every call site into rapidjson processes only fully trusted input.

This is straightforward enough for a point-in-time assessment, but it does represent ongoing cost, and we don't want to risk introducing new call sites into this logic where we can't guarantee that the input is trustworthy. To facilitate this, I recommend renaming our rapidjson wrapping utility methods to parse_fully_trusted_raw_data and parse_fully_trusted_file, which clearly indicate at the invocation site that the caller passes only fully trustworthy data. This should reduce the risk of us violating the trust contract going forward and should simplify future attestations.

Additionally, we received internal bug reports of in situ parsing causing AVs when parsing improperly formatted JSON payloads. rapidjson's in situ parser requires a mutable null-terminated string; and since we use mmap files, we can't guarantee the presence of a null terminator. This means that if the file length happens to exactly match the page size of the underlying OS, and if the underlying JSON blob has an error (like missing the closing bracket), rapidjson will try to read into unmapped memory and crash the process rather than report an actionable error.

This is resolved by passing an explicit length to the parse routine. However, since ParseInsitu doesn't provide an overload that takes an explicit length, we should fall back to normal parsing.

Note to reviewers: If we really do want to enable in-situ parsing, we could always create our own GenericInsituStringStream-like type which takes a length and call ParseStream, passing kParseInsituFlag as a template arg. We can't use MemoryStream directly since it's read-only.

(Jeff, this is what I pinged you about via IM.)

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.

Files not reviewed (7)
  • src/native/corehost/comhost/clsidmap.cpp: Language not supported
  • src/native/corehost/fxr/sdk_resolver.cpp: Language not supported
  • src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp: Language not supported
  • src/native/corehost/hostpolicy/deps_format.cpp: Language not supported
  • src/native/corehost/json_parser.cpp: Language not supported
  • src/native/corehost/json_parser.h: Language not supported
  • src/native/corehost/runtime_config.cpp: Language not supported

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @vitek-karas, @agocke, @VSadov
See info in area-owners.md if you want to be subscribed.

@GrabYourPitchforks

Copy link
Copy Markdown
MemberAuthor

Rebased on latest main to resolve merge conflicts.

CopilotAI review requested due to automatic review settings March 24, 2026 19:01
@GrabYourPitchforksGrabYourPitchforks changed the title Rename rapidjson helper functionsRename rapidjson helper functions; remove in situ parsingMar 24, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

src/native/corehost/json_parser.cpp:114

  • In parse_fully_trusted_file, m_data = (char*)pal::mmap_read(...) casts away const from a read-only mapping. Even though current parsing is non-mutating, this makes it easy to accidentally introduce writes later (which would fault on Unix/Windows). Prefer keeping the mapping pointer const throughout and only using mutable mappings when truly required.
 if (m_data == nullptr)
{
m_data = (char*)pal::mmap_read(path, &m_size);
if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;
}

src/native/corehost/json_parser.cpp:113

  • The error text "Cannot use file stream for [...]" is misleading here since the code path is attempting a memory-map (mmap_read), not a file stream. Consider updating the message to reflect the actual operation (mapping the file) to make diagnostics clearer.
 if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;

src/native/corehost/json_parser.cpp:36

  • get_line_column_from_offset can read past the end of the buffer: when i == size - 1 (possible when offset == size), the data[i + 1] access in the CRLF check is out-of-bounds. Add a bounds check (e.g., ensure i + 1 < size) before reading data[i + 1] so malformed/edge inputs don’t trigger an OOB read while formatting parse errors.
void get_line_column_from_offset(const char* data, size_t size, size_t offset, int *line, int *column)
{
assert(offset <= size);
*line = *column = 1;
for (size_t i = 0; i < offset; i++)
{
(*column)++;
if (data[i] == '\n')
{
(*line)++;
*column = 1;
}
else if (data[i] == '\r' && data[i + 1] == '\n')
{

src/native/corehost/json_parser.h:52

  • pal::mmap_read returns a const void* (read-only mapping), but m_data is a mutable char* and the code casts away const when assigning the mapping. Since parsing no longer uses in-situ mutation, consider making m_data a const char* and updating parse_fully_trusted_raw_data to take const char* to avoid misleading mutability and accidental writes to a read-only mapping.
 bool parse_fully_trusted_raw_data(char* data, size_t size, const pal::string_t& context);
bool parse_fully_trusted_file(const pal::string_t& path);
json_parser_t()
: m_data(nullptr)
, m_bundle_location(nullptr) {}
~json_parser_t();
private:
char* m_data; // The memory mapped bytes of the file
size_t m_size; // Size of the mapped memory

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/corehost/json_parser.cpp:40

  • In get_line_column_from_offset, the CRLF detection now checks (i + 1) < offset instead of guarding against the end of the buffer. This can under-count newlines when the error offset points at the \n of a CRLF pair, and it’s not necessary for bounds safety (the real bound is size). Consider changing the condition to guard with size so CRLF is recognized when present while still avoiding an out-of-bounds read.
 else if (data[i] == '\r' && (i + 1) < offset && data[i + 1] == '\n')
{
(*line)++;
*column = 1;
i++; // Discard carriage return

Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@pavelsavara

pavelsavara commented Mar 25, 2026

Copy link
Copy Markdown
Member

I will comment from browser/wasm perspective.

  • I believe that main use-case for json parser in coreCLR codebase is to parse runtimeconfig.json
  • in browser the host is statically linked with the runtime, the the part that chooses runtime version doesn't apply.
  • so we only need to know values of configProperties and that's is (string,string)[] list of pair of strings.
  • I think it's like that for all "mobile" targets.
  • in browserhost for CoreCLR we use browser json parser to parse much larger manifest, and configProperties are part of that. The JS side will transform that directly into appctx_keys, appctx_values C UTF8 char**
  • in mono for browser it's also like that

for(const[key,value]ofruntimeConfigProperties.entries()){
constkeyPtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(key);
constvaluePtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(value);
_ems_.dotnetApi.setHeapU32((appctx_keysasany)+(propertyCount*sizeOfPtr),keyPtr);
_ems_.dotnetApi.setHeapU32((appctx_valuesasany)+(propertyCount*sizeOfPtr),valuePtr);
propertyCount++;
buffers.push(keyPtrasany);
buffers.push(valuePtrasany);
}

construntimeConfigProperties=newMap<string,string>();
if(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties){
for(const[key,value]ofObject.entries(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties)){
runtimeConfigProperties.set(key,""+value);
}
}
runtimeConfigProperties.set("APP_CONTEXT_BASE_DIRECTORY","/");
runtimeConfigProperties.set("RUNTIME_IDENTIFIER","browser-wasm");
constpropertyCount=runtimeConfigProperties.size;
constbuffers:VoidPtr[]=[];
constappctx_keys=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;
constappctx_values=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;

In the past we avoided need for json parser in the VM, by converting into very simple binary file at compile time.
For browser I got rid of that in #115113

But I still see the same idea on apple/android

if(BundledRuntimeConfig?.ItemSpec!=null)
{
dataSymbol=BundledRuntimeConfig.GetMetadata("DataSymbol");
if(string.IsNullOrEmpty(dataSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataSymbol' metadata.");
}
dataLenSymbol=BundledRuntimeConfig.GetMetadata("DataLenSymbol");
if(string.IsNullOrEmpty(dataLenSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataLenSymbol' metadata.");
}
externBundledResourcesSymbols.AppendLine();
externBundledResourcesSymbols.AppendLine($"extern uint8_t {dataSymbol}[];");
externBundledResourcesSymbols.AppendLine($"extern const uint32_t {dataLenSymbol};");
}

In the future, we may still need this compile time trick for WASI, but just for the configProperties part.

So my angle is, let's delete it, it makes coreCLR binary larger download for the browser.
(I realize normal OS story is different)

@jkotas

Copy link
Copy Markdown
Member

it makes coreCLR binary larger download for the browser.

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

@pavelsavara

Copy link
Copy Markdown
Member

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

host and vm are the same binary for wasm, but I stand corrected, we are NOT building/linking it in the browser.

if(NOT CLR_CMAKE_TARGET_BROWSER)
add_library(fxr_resolverINTERFACE)
target_sources(fxr_resolverINTERFACEfxr_resolver.cpp)
target_include_directories(fxr_resolverINTERFACEfxr)
add_compile_definitions(RAPIDJSON_HAS_CXX17)
if ((NOTDEFINED CLR_CMAKE_USE_SYSTEM_RAPIDJSON) OR (NOT CLR_CMAKE_USE_SYSTEM_RAPIDJSON))
include_directories(${CLR_SRC_NATIVE_DIR}/external/)
endif()

I don't know about other single-file hosts.

@GrabYourPitchforks

GrabYourPitchforks commented Mar 27, 2026

Copy link
Copy Markdown
MemberAuthor

Elinor requested offline that I run the startup perf benchmarks. Should get to that next week.

Edit: No measurable change. 60 ~ 62 ms on my machine to launch emptycsconsoletemplate, regardless of whether the runtime comes from main or from this PR's branch. I also confirmed the perf scaffolding drops a runtimeconfig.json file alongside the executable, which I tweaked to target net11:

{
"runtimeOptions": {
"tfm": "net11.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "11.0.0"
},
"configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

CopilotAI review requested due to automatic review settings July 7, 2026 23:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/corehost/json_parser.h
Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@GrabYourPitchforks
GrabYourPitchforks merged commit 0f9f04b into dotnet:mainJul 8, 2026
172 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
@am11am11 mentioned this pull request Jul 24, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@GrabYourPitchforks@pavelsavara@jkotas@elinor-fung@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Rename rapidjson helper functions; remove in situ parsing by GrabYourPitchforks · Pull Request #114017 · dotnet/runtime · GitHub
Skip to content

Rename rapidjson helper functions; remove in situ parsing - #114017

Merged
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson
Jul 8, 2026
Merged

Rename rapidjson helper functions; remove in situ parsing#114017
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson

Conversation

@GrabYourPitchforks

@GrabYourPitchforksGrabYourPitchforks commented Mar 28, 2025

Copy link
Copy Markdown
Member

Since rapidjson is a disallowed deserializer within Microsoft, signing off on .NET 10 will require us to attest that every call site into rapidjson processes only fully trusted input.

This is straightforward enough for a point-in-time assessment, but it does represent ongoing cost, and we don't want to risk introducing new call sites into this logic where we can't guarantee that the input is trustworthy. To facilitate this, I recommend renaming our rapidjson wrapping utility methods to parse_fully_trusted_raw_data and parse_fully_trusted_file, which clearly indicate at the invocation site that the caller passes only fully trustworthy data. This should reduce the risk of us violating the trust contract going forward and should simplify future attestations.

Additionally, we received internal bug reports of in situ parsing causing AVs when parsing improperly formatted JSON payloads. rapidjson's in situ parser requires a mutable null-terminated string; and since we use mmap files, we can't guarantee the presence of a null terminator. This means that if the file length happens to exactly match the page size of the underlying OS, and if the underlying JSON blob has an error (like missing the closing bracket), rapidjson will try to read into unmapped memory and crash the process rather than report an actionable error.

This is resolved by passing an explicit length to the parse routine. However, since ParseInsitu doesn't provide an overload that takes an explicit length, we should fall back to normal parsing.

Note to reviewers: If we really do want to enable in-situ parsing, we could always create our own GenericInsituStringStream-like type which takes a length and call ParseStream, passing kParseInsituFlag as a template arg. We can't use MemoryStream directly since it's read-only.

(Jeff, this is what I pinged you about via IM.)

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.

Files not reviewed (7)
  • src/native/corehost/comhost/clsidmap.cpp: Language not supported
  • src/native/corehost/fxr/sdk_resolver.cpp: Language not supported
  • src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp: Language not supported
  • src/native/corehost/hostpolicy/deps_format.cpp: Language not supported
  • src/native/corehost/json_parser.cpp: Language not supported
  • src/native/corehost/json_parser.h: Language not supported
  • src/native/corehost/runtime_config.cpp: Language not supported

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @vitek-karas, @agocke, @VSadov
See info in area-owners.md if you want to be subscribed.

@GrabYourPitchforks

Copy link
Copy Markdown
MemberAuthor

Rebased on latest main to resolve merge conflicts.

CopilotAI review requested due to automatic review settings March 24, 2026 19:01
@GrabYourPitchforksGrabYourPitchforks changed the title Rename rapidjson helper functionsRename rapidjson helper functions; remove in situ parsingMar 24, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

src/native/corehost/json_parser.cpp:114

  • In parse_fully_trusted_file, m_data = (char*)pal::mmap_read(...) casts away const from a read-only mapping. Even though current parsing is non-mutating, this makes it easy to accidentally introduce writes later (which would fault on Unix/Windows). Prefer keeping the mapping pointer const throughout and only using mutable mappings when truly required.
 if (m_data == nullptr)
{
m_data = (char*)pal::mmap_read(path, &m_size);
if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;
}

src/native/corehost/json_parser.cpp:113

  • The error text "Cannot use file stream for [...]" is misleading here since the code path is attempting a memory-map (mmap_read), not a file stream. Consider updating the message to reflect the actual operation (mapping the file) to make diagnostics clearer.
 if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;

src/native/corehost/json_parser.cpp:36

  • get_line_column_from_offset can read past the end of the buffer: when i == size - 1 (possible when offset == size), the data[i + 1] access in the CRLF check is out-of-bounds. Add a bounds check (e.g., ensure i + 1 < size) before reading data[i + 1] so malformed/edge inputs don’t trigger an OOB read while formatting parse errors.
void get_line_column_from_offset(const char* data, size_t size, size_t offset, int *line, int *column)
{
assert(offset <= size);
*line = *column = 1;
for (size_t i = 0; i < offset; i++)
{
(*column)++;
if (data[i] == '\n')
{
(*line)++;
*column = 1;
}
else if (data[i] == '\r' && data[i + 1] == '\n')
{

src/native/corehost/json_parser.h:52

  • pal::mmap_read returns a const void* (read-only mapping), but m_data is a mutable char* and the code casts away const when assigning the mapping. Since parsing no longer uses in-situ mutation, consider making m_data a const char* and updating parse_fully_trusted_raw_data to take const char* to avoid misleading mutability and accidental writes to a read-only mapping.
 bool parse_fully_trusted_raw_data(char* data, size_t size, const pal::string_t& context);
bool parse_fully_trusted_file(const pal::string_t& path);
json_parser_t()
: m_data(nullptr)
, m_bundle_location(nullptr) {}
~json_parser_t();
private:
char* m_data; // The memory mapped bytes of the file
size_t m_size; // Size of the mapped memory

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/corehost/json_parser.cpp:40

  • In get_line_column_from_offset, the CRLF detection now checks (i + 1) < offset instead of guarding against the end of the buffer. This can under-count newlines when the error offset points at the \n of a CRLF pair, and it’s not necessary for bounds safety (the real bound is size). Consider changing the condition to guard with size so CRLF is recognized when present while still avoiding an out-of-bounds read.
 else if (data[i] == '\r' && (i + 1) < offset && data[i + 1] == '\n')
{
(*line)++;
*column = 1;
i++; // Discard carriage return

Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@pavelsavara

pavelsavara commented Mar 25, 2026

Copy link
Copy Markdown
Member

I will comment from browser/wasm perspective.

  • I believe that main use-case for json parser in coreCLR codebase is to parse runtimeconfig.json
  • in browser the host is statically linked with the runtime, the the part that chooses runtime version doesn't apply.
  • so we only need to know values of configProperties and that's is (string,string)[] list of pair of strings.
  • I think it's like that for all "mobile" targets.
  • in browserhost for CoreCLR we use browser json parser to parse much larger manifest, and configProperties are part of that. The JS side will transform that directly into appctx_keys, appctx_values C UTF8 char**
  • in mono for browser it's also like that

for(const[key,value]ofruntimeConfigProperties.entries()){
constkeyPtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(key);
constvaluePtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(value);
_ems_.dotnetApi.setHeapU32((appctx_keysasany)+(propertyCount*sizeOfPtr),keyPtr);
_ems_.dotnetApi.setHeapU32((appctx_valuesasany)+(propertyCount*sizeOfPtr),valuePtr);
propertyCount++;
buffers.push(keyPtrasany);
buffers.push(valuePtrasany);
}

construntimeConfigProperties=newMap<string,string>();
if(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties){
for(const[key,value]ofObject.entries(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties)){
runtimeConfigProperties.set(key,""+value);
}
}
runtimeConfigProperties.set("APP_CONTEXT_BASE_DIRECTORY","/");
runtimeConfigProperties.set("RUNTIME_IDENTIFIER","browser-wasm");
constpropertyCount=runtimeConfigProperties.size;
constbuffers:VoidPtr[]=[];
constappctx_keys=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;
constappctx_values=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;

In the past we avoided need for json parser in the VM, by converting into very simple binary file at compile time.
For browser I got rid of that in #115113

But I still see the same idea on apple/android

if(BundledRuntimeConfig?.ItemSpec!=null)
{
dataSymbol=BundledRuntimeConfig.GetMetadata("DataSymbol");
if(string.IsNullOrEmpty(dataSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataSymbol' metadata.");
}
dataLenSymbol=BundledRuntimeConfig.GetMetadata("DataLenSymbol");
if(string.IsNullOrEmpty(dataLenSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataLenSymbol' metadata.");
}
externBundledResourcesSymbols.AppendLine();
externBundledResourcesSymbols.AppendLine($"extern uint8_t {dataSymbol}[];");
externBundledResourcesSymbols.AppendLine($"extern const uint32_t {dataLenSymbol};");
}

In the future, we may still need this compile time trick for WASI, but just for the configProperties part.

So my angle is, let's delete it, it makes coreCLR binary larger download for the browser.
(I realize normal OS story is different)

@jkotas

Copy link
Copy Markdown
Member

it makes coreCLR binary larger download for the browser.

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

@pavelsavara

Copy link
Copy Markdown
Member

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

host and vm are the same binary for wasm, but I stand corrected, we are NOT building/linking it in the browser.

if(NOT CLR_CMAKE_TARGET_BROWSER)
add_library(fxr_resolverINTERFACE)
target_sources(fxr_resolverINTERFACEfxr_resolver.cpp)
target_include_directories(fxr_resolverINTERFACEfxr)
add_compile_definitions(RAPIDJSON_HAS_CXX17)
if ((NOTDEFINED CLR_CMAKE_USE_SYSTEM_RAPIDJSON) OR (NOT CLR_CMAKE_USE_SYSTEM_RAPIDJSON))
include_directories(${CLR_SRC_NATIVE_DIR}/external/)
endif()

I don't know about other single-file hosts.

@GrabYourPitchforks

GrabYourPitchforks commented Mar 27, 2026

Copy link
Copy Markdown
MemberAuthor

Elinor requested offline that I run the startup perf benchmarks. Should get to that next week.

Edit: No measurable change. 60 ~ 62 ms on my machine to launch emptycsconsoletemplate, regardless of whether the runtime comes from main or from this PR's branch. I also confirmed the perf scaffolding drops a runtimeconfig.json file alongside the executable, which I tweaked to target net11:

{
"runtimeOptions": {
"tfm": "net11.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "11.0.0"
},
"configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

CopilotAI review requested due to automatic review settings July 7, 2026 23:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/corehost/json_parser.h
Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@GrabYourPitchforks
GrabYourPitchforks merged commit 0f9f04b into dotnet:mainJul 8, 2026
172 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
@am11am11 mentioned this pull request Jul 24, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@GrabYourPitchforks@pavelsavara@jkotas@elinor-fung@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Rename rapidjson helper functions; remove in situ parsing by GrabYourPitchforks · Pull Request #114017 · dotnet/runtime · GitHub
Skip to content

Rename rapidjson helper functions; remove in situ parsing - #114017

Merged
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson
Jul 8, 2026
Merged

Rename rapidjson helper functions; remove in situ parsing#114017
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson

Conversation

@GrabYourPitchforks

@GrabYourPitchforksGrabYourPitchforks commented Mar 28, 2025

Copy link
Copy Markdown
Member

Since rapidjson is a disallowed deserializer within Microsoft, signing off on .NET 10 will require us to attest that every call site into rapidjson processes only fully trusted input.

This is straightforward enough for a point-in-time assessment, but it does represent ongoing cost, and we don't want to risk introducing new call sites into this logic where we can't guarantee that the input is trustworthy. To facilitate this, I recommend renaming our rapidjson wrapping utility methods to parse_fully_trusted_raw_data and parse_fully_trusted_file, which clearly indicate at the invocation site that the caller passes only fully trustworthy data. This should reduce the risk of us violating the trust contract going forward and should simplify future attestations.

Additionally, we received internal bug reports of in situ parsing causing AVs when parsing improperly formatted JSON payloads. rapidjson's in situ parser requires a mutable null-terminated string; and since we use mmap files, we can't guarantee the presence of a null terminator. This means that if the file length happens to exactly match the page size of the underlying OS, and if the underlying JSON blob has an error (like missing the closing bracket), rapidjson will try to read into unmapped memory and crash the process rather than report an actionable error.

This is resolved by passing an explicit length to the parse routine. However, since ParseInsitu doesn't provide an overload that takes an explicit length, we should fall back to normal parsing.

Note to reviewers: If we really do want to enable in-situ parsing, we could always create our own GenericInsituStringStream-like type which takes a length and call ParseStream, passing kParseInsituFlag as a template arg. We can't use MemoryStream directly since it's read-only.

(Jeff, this is what I pinged you about via IM.)

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.

Files not reviewed (7)
  • src/native/corehost/comhost/clsidmap.cpp: Language not supported
  • src/native/corehost/fxr/sdk_resolver.cpp: Language not supported
  • src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp: Language not supported
  • src/native/corehost/hostpolicy/deps_format.cpp: Language not supported
  • src/native/corehost/json_parser.cpp: Language not supported
  • src/native/corehost/json_parser.h: Language not supported
  • src/native/corehost/runtime_config.cpp: Language not supported

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @vitek-karas, @agocke, @VSadov
See info in area-owners.md if you want to be subscribed.

@GrabYourPitchforks

Copy link
Copy Markdown
MemberAuthor

Rebased on latest main to resolve merge conflicts.

CopilotAI review requested due to automatic review settings March 24, 2026 19:01
@GrabYourPitchforksGrabYourPitchforks changed the title Rename rapidjson helper functionsRename rapidjson helper functions; remove in situ parsingMar 24, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

src/native/corehost/json_parser.cpp:114

  • In parse_fully_trusted_file, m_data = (char*)pal::mmap_read(...) casts away const from a read-only mapping. Even though current parsing is non-mutating, this makes it easy to accidentally introduce writes later (which would fault on Unix/Windows). Prefer keeping the mapping pointer const throughout and only using mutable mappings when truly required.
 if (m_data == nullptr)
{
m_data = (char*)pal::mmap_read(path, &m_size);
if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;
}

src/native/corehost/json_parser.cpp:113

  • The error text "Cannot use file stream for [...]" is misleading here since the code path is attempting a memory-map (mmap_read), not a file stream. Consider updating the message to reflect the actual operation (mapping the file) to make diagnostics clearer.
 if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;

src/native/corehost/json_parser.cpp:36

  • get_line_column_from_offset can read past the end of the buffer: when i == size - 1 (possible when offset == size), the data[i + 1] access in the CRLF check is out-of-bounds. Add a bounds check (e.g., ensure i + 1 < size) before reading data[i + 1] so malformed/edge inputs don’t trigger an OOB read while formatting parse errors.
void get_line_column_from_offset(const char* data, size_t size, size_t offset, int *line, int *column)
{
assert(offset <= size);
*line = *column = 1;
for (size_t i = 0; i < offset; i++)
{
(*column)++;
if (data[i] == '\n')
{
(*line)++;
*column = 1;
}
else if (data[i] == '\r' && data[i + 1] == '\n')
{

src/native/corehost/json_parser.h:52

  • pal::mmap_read returns a const void* (read-only mapping), but m_data is a mutable char* and the code casts away const when assigning the mapping. Since parsing no longer uses in-situ mutation, consider making m_data a const char* and updating parse_fully_trusted_raw_data to take const char* to avoid misleading mutability and accidental writes to a read-only mapping.
 bool parse_fully_trusted_raw_data(char* data, size_t size, const pal::string_t& context);
bool parse_fully_trusted_file(const pal::string_t& path);
json_parser_t()
: m_data(nullptr)
, m_bundle_location(nullptr) {}
~json_parser_t();
private:
char* m_data; // The memory mapped bytes of the file
size_t m_size; // Size of the mapped memory

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/corehost/json_parser.cpp:40

  • In get_line_column_from_offset, the CRLF detection now checks (i + 1) < offset instead of guarding against the end of the buffer. This can under-count newlines when the error offset points at the \n of a CRLF pair, and it’s not necessary for bounds safety (the real bound is size). Consider changing the condition to guard with size so CRLF is recognized when present while still avoiding an out-of-bounds read.
 else if (data[i] == '\r' && (i + 1) < offset && data[i + 1] == '\n')
{
(*line)++;
*column = 1;
i++; // Discard carriage return

Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@pavelsavara

pavelsavara commented Mar 25, 2026

Copy link
Copy Markdown
Member

I will comment from browser/wasm perspective.

  • I believe that main use-case for json parser in coreCLR codebase is to parse runtimeconfig.json
  • in browser the host is statically linked with the runtime, the the part that chooses runtime version doesn't apply.
  • so we only need to know values of configProperties and that's is (string,string)[] list of pair of strings.
  • I think it's like that for all "mobile" targets.
  • in browserhost for CoreCLR we use browser json parser to parse much larger manifest, and configProperties are part of that. The JS side will transform that directly into appctx_keys, appctx_values C UTF8 char**
  • in mono for browser it's also like that

for(const[key,value]ofruntimeConfigProperties.entries()){
constkeyPtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(key);
constvaluePtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(value);
_ems_.dotnetApi.setHeapU32((appctx_keysasany)+(propertyCount*sizeOfPtr),keyPtr);
_ems_.dotnetApi.setHeapU32((appctx_valuesasany)+(propertyCount*sizeOfPtr),valuePtr);
propertyCount++;
buffers.push(keyPtrasany);
buffers.push(valuePtrasany);
}

construntimeConfigProperties=newMap<string,string>();
if(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties){
for(const[key,value]ofObject.entries(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties)){
runtimeConfigProperties.set(key,""+value);
}
}
runtimeConfigProperties.set("APP_CONTEXT_BASE_DIRECTORY","/");
runtimeConfigProperties.set("RUNTIME_IDENTIFIER","browser-wasm");
constpropertyCount=runtimeConfigProperties.size;
constbuffers:VoidPtr[]=[];
constappctx_keys=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;
constappctx_values=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;

In the past we avoided need for json parser in the VM, by converting into very simple binary file at compile time.
For browser I got rid of that in #115113

But I still see the same idea on apple/android

if(BundledRuntimeConfig?.ItemSpec!=null)
{
dataSymbol=BundledRuntimeConfig.GetMetadata("DataSymbol");
if(string.IsNullOrEmpty(dataSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataSymbol' metadata.");
}
dataLenSymbol=BundledRuntimeConfig.GetMetadata("DataLenSymbol");
if(string.IsNullOrEmpty(dataLenSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataLenSymbol' metadata.");
}
externBundledResourcesSymbols.AppendLine();
externBundledResourcesSymbols.AppendLine($"extern uint8_t {dataSymbol}[];");
externBundledResourcesSymbols.AppendLine($"extern const uint32_t {dataLenSymbol};");
}

In the future, we may still need this compile time trick for WASI, but just for the configProperties part.

So my angle is, let's delete it, it makes coreCLR binary larger download for the browser.
(I realize normal OS story is different)

@jkotas

Copy link
Copy Markdown
Member

it makes coreCLR binary larger download for the browser.

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

@pavelsavara

Copy link
Copy Markdown
Member

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

host and vm are the same binary for wasm, but I stand corrected, we are NOT building/linking it in the browser.

if(NOT CLR_CMAKE_TARGET_BROWSER)
add_library(fxr_resolverINTERFACE)
target_sources(fxr_resolverINTERFACEfxr_resolver.cpp)
target_include_directories(fxr_resolverINTERFACEfxr)
add_compile_definitions(RAPIDJSON_HAS_CXX17)
if ((NOTDEFINED CLR_CMAKE_USE_SYSTEM_RAPIDJSON) OR (NOT CLR_CMAKE_USE_SYSTEM_RAPIDJSON))
include_directories(${CLR_SRC_NATIVE_DIR}/external/)
endif()

I don't know about other single-file hosts.

@GrabYourPitchforks

GrabYourPitchforks commented Mar 27, 2026

Copy link
Copy Markdown
MemberAuthor

Elinor requested offline that I run the startup perf benchmarks. Should get to that next week.

Edit: No measurable change. 60 ~ 62 ms on my machine to launch emptycsconsoletemplate, regardless of whether the runtime comes from main or from this PR's branch. I also confirmed the perf scaffolding drops a runtimeconfig.json file alongside the executable, which I tweaked to target net11:

{
"runtimeOptions": {
"tfm": "net11.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "11.0.0"
},
"configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

CopilotAI review requested due to automatic review settings July 7, 2026 23:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/corehost/json_parser.h
Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@GrabYourPitchforks
GrabYourPitchforks merged commit 0f9f04b into dotnet:mainJul 8, 2026
172 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
@am11am11 mentioned this pull request Jul 24, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@GrabYourPitchforks@pavelsavara@jkotas@elinor-fung@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Rename rapidjson helper functions; remove in situ parsing by GrabYourPitchforks · Pull Request #114017 · dotnet/runtime · GitHub
Skip to content

Rename rapidjson helper functions; remove in situ parsing - #114017

Merged
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson
Jul 8, 2026
Merged

Rename rapidjson helper functions; remove in situ parsing#114017
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson

Conversation

@GrabYourPitchforks

@GrabYourPitchforksGrabYourPitchforks commented Mar 28, 2025

Copy link
Copy Markdown
Member

Since rapidjson is a disallowed deserializer within Microsoft, signing off on .NET 10 will require us to attest that every call site into rapidjson processes only fully trusted input.

This is straightforward enough for a point-in-time assessment, but it does represent ongoing cost, and we don't want to risk introducing new call sites into this logic where we can't guarantee that the input is trustworthy. To facilitate this, I recommend renaming our rapidjson wrapping utility methods to parse_fully_trusted_raw_data and parse_fully_trusted_file, which clearly indicate at the invocation site that the caller passes only fully trustworthy data. This should reduce the risk of us violating the trust contract going forward and should simplify future attestations.

Additionally, we received internal bug reports of in situ parsing causing AVs when parsing improperly formatted JSON payloads. rapidjson's in situ parser requires a mutable null-terminated string; and since we use mmap files, we can't guarantee the presence of a null terminator. This means that if the file length happens to exactly match the page size of the underlying OS, and if the underlying JSON blob has an error (like missing the closing bracket), rapidjson will try to read into unmapped memory and crash the process rather than report an actionable error.

This is resolved by passing an explicit length to the parse routine. However, since ParseInsitu doesn't provide an overload that takes an explicit length, we should fall back to normal parsing.

Note to reviewers: If we really do want to enable in-situ parsing, we could always create our own GenericInsituStringStream-like type which takes a length and call ParseStream, passing kParseInsituFlag as a template arg. We can't use MemoryStream directly since it's read-only.

(Jeff, this is what I pinged you about via IM.)

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.

Files not reviewed (7)
  • src/native/corehost/comhost/clsidmap.cpp: Language not supported
  • src/native/corehost/fxr/sdk_resolver.cpp: Language not supported
  • src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp: Language not supported
  • src/native/corehost/hostpolicy/deps_format.cpp: Language not supported
  • src/native/corehost/json_parser.cpp: Language not supported
  • src/native/corehost/json_parser.h: Language not supported
  • src/native/corehost/runtime_config.cpp: Language not supported

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @vitek-karas, @agocke, @VSadov
See info in area-owners.md if you want to be subscribed.

@GrabYourPitchforks

Copy link
Copy Markdown
MemberAuthor

Rebased on latest main to resolve merge conflicts.

CopilotAI review requested due to automatic review settings March 24, 2026 19:01
@GrabYourPitchforksGrabYourPitchforks changed the title Rename rapidjson helper functionsRename rapidjson helper functions; remove in situ parsingMar 24, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

src/native/corehost/json_parser.cpp:114

  • In parse_fully_trusted_file, m_data = (char*)pal::mmap_read(...) casts away const from a read-only mapping. Even though current parsing is non-mutating, this makes it easy to accidentally introduce writes later (which would fault on Unix/Windows). Prefer keeping the mapping pointer const throughout and only using mutable mappings when truly required.
 if (m_data == nullptr)
{
m_data = (char*)pal::mmap_read(path, &m_size);
if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;
}

src/native/corehost/json_parser.cpp:113

  • The error text "Cannot use file stream for [...]" is misleading here since the code path is attempting a memory-map (mmap_read), not a file stream. Consider updating the message to reflect the actual operation (mapping the file) to make diagnostics clearer.
 if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;

src/native/corehost/json_parser.cpp:36

  • get_line_column_from_offset can read past the end of the buffer: when i == size - 1 (possible when offset == size), the data[i + 1] access in the CRLF check is out-of-bounds. Add a bounds check (e.g., ensure i + 1 < size) before reading data[i + 1] so malformed/edge inputs don’t trigger an OOB read while formatting parse errors.
void get_line_column_from_offset(const char* data, size_t size, size_t offset, int *line, int *column)
{
assert(offset <= size);
*line = *column = 1;
for (size_t i = 0; i < offset; i++)
{
(*column)++;
if (data[i] == '\n')
{
(*line)++;
*column = 1;
}
else if (data[i] == '\r' && data[i + 1] == '\n')
{

src/native/corehost/json_parser.h:52

  • pal::mmap_read returns a const void* (read-only mapping), but m_data is a mutable char* and the code casts away const when assigning the mapping. Since parsing no longer uses in-situ mutation, consider making m_data a const char* and updating parse_fully_trusted_raw_data to take const char* to avoid misleading mutability and accidental writes to a read-only mapping.
 bool parse_fully_trusted_raw_data(char* data, size_t size, const pal::string_t& context);
bool parse_fully_trusted_file(const pal::string_t& path);
json_parser_t()
: m_data(nullptr)
, m_bundle_location(nullptr) {}
~json_parser_t();
private:
char* m_data; // The memory mapped bytes of the file
size_t m_size; // Size of the mapped memory

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/corehost/json_parser.cpp:40

  • In get_line_column_from_offset, the CRLF detection now checks (i + 1) < offset instead of guarding against the end of the buffer. This can under-count newlines when the error offset points at the \n of a CRLF pair, and it’s not necessary for bounds safety (the real bound is size). Consider changing the condition to guard with size so CRLF is recognized when present while still avoiding an out-of-bounds read.
 else if (data[i] == '\r' && (i + 1) < offset && data[i + 1] == '\n')
{
(*line)++;
*column = 1;
i++; // Discard carriage return

Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@pavelsavara

pavelsavara commented Mar 25, 2026

Copy link
Copy Markdown
Member

I will comment from browser/wasm perspective.

  • I believe that main use-case for json parser in coreCLR codebase is to parse runtimeconfig.json
  • in browser the host is statically linked with the runtime, the the part that chooses runtime version doesn't apply.
  • so we only need to know values of configProperties and that's is (string,string)[] list of pair of strings.
  • I think it's like that for all "mobile" targets.
  • in browserhost for CoreCLR we use browser json parser to parse much larger manifest, and configProperties are part of that. The JS side will transform that directly into appctx_keys, appctx_values C UTF8 char**
  • in mono for browser it's also like that

for(const[key,value]ofruntimeConfigProperties.entries()){
constkeyPtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(key);
constvaluePtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(value);
_ems_.dotnetApi.setHeapU32((appctx_keysasany)+(propertyCount*sizeOfPtr),keyPtr);
_ems_.dotnetApi.setHeapU32((appctx_valuesasany)+(propertyCount*sizeOfPtr),valuePtr);
propertyCount++;
buffers.push(keyPtrasany);
buffers.push(valuePtrasany);
}

construntimeConfigProperties=newMap<string,string>();
if(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties){
for(const[key,value]ofObject.entries(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties)){
runtimeConfigProperties.set(key,""+value);
}
}
runtimeConfigProperties.set("APP_CONTEXT_BASE_DIRECTORY","/");
runtimeConfigProperties.set("RUNTIME_IDENTIFIER","browser-wasm");
constpropertyCount=runtimeConfigProperties.size;
constbuffers:VoidPtr[]=[];
constappctx_keys=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;
constappctx_values=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;

In the past we avoided need for json parser in the VM, by converting into very simple binary file at compile time.
For browser I got rid of that in #115113

But I still see the same idea on apple/android

if(BundledRuntimeConfig?.ItemSpec!=null)
{
dataSymbol=BundledRuntimeConfig.GetMetadata("DataSymbol");
if(string.IsNullOrEmpty(dataSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataSymbol' metadata.");
}
dataLenSymbol=BundledRuntimeConfig.GetMetadata("DataLenSymbol");
if(string.IsNullOrEmpty(dataLenSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataLenSymbol' metadata.");
}
externBundledResourcesSymbols.AppendLine();
externBundledResourcesSymbols.AppendLine($"extern uint8_t {dataSymbol}[];");
externBundledResourcesSymbols.AppendLine($"extern const uint32_t {dataLenSymbol};");
}

In the future, we may still need this compile time trick for WASI, but just for the configProperties part.

So my angle is, let's delete it, it makes coreCLR binary larger download for the browser.
(I realize normal OS story is different)

@jkotas

Copy link
Copy Markdown
Member

it makes coreCLR binary larger download for the browser.

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

@pavelsavara

Copy link
Copy Markdown
Member

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

host and vm are the same binary for wasm, but I stand corrected, we are NOT building/linking it in the browser.

if(NOT CLR_CMAKE_TARGET_BROWSER)
add_library(fxr_resolverINTERFACE)
target_sources(fxr_resolverINTERFACEfxr_resolver.cpp)
target_include_directories(fxr_resolverINTERFACEfxr)
add_compile_definitions(RAPIDJSON_HAS_CXX17)
if ((NOTDEFINED CLR_CMAKE_USE_SYSTEM_RAPIDJSON) OR (NOT CLR_CMAKE_USE_SYSTEM_RAPIDJSON))
include_directories(${CLR_SRC_NATIVE_DIR}/external/)
endif()

I don't know about other single-file hosts.

@GrabYourPitchforks

GrabYourPitchforks commented Mar 27, 2026

Copy link
Copy Markdown
MemberAuthor

Elinor requested offline that I run the startup perf benchmarks. Should get to that next week.

Edit: No measurable change. 60 ~ 62 ms on my machine to launch emptycsconsoletemplate, regardless of whether the runtime comes from main or from this PR's branch. I also confirmed the perf scaffolding drops a runtimeconfig.json file alongside the executable, which I tweaked to target net11:

{
"runtimeOptions": {
"tfm": "net11.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "11.0.0"
},
"configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

CopilotAI review requested due to automatic review settings July 7, 2026 23:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/corehost/json_parser.h
Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@GrabYourPitchforks
GrabYourPitchforks merged commit 0f9f04b into dotnet:mainJul 8, 2026
172 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
@am11am11 mentioned this pull request Jul 24, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@GrabYourPitchforks@pavelsavara@jkotas@elinor-fung@karelz
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Rename rapidjson helper functions; remove in situ parsing by GrabYourPitchforks · Pull Request #114017 · dotnet/runtime · GitHub
Skip to content

Rename rapidjson helper functions; remove in situ parsing - #114017

Merged
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson
Jul 8, 2026
Merged

Rename rapidjson helper functions; remove in situ parsing#114017
GrabYourPitchforks merged 5 commits into
dotnet:mainfrom
GrabYourPitchforks:rapidjson

Conversation

@GrabYourPitchforks

@GrabYourPitchforksGrabYourPitchforks commented Mar 28, 2025

Copy link
Copy Markdown
Member

Since rapidjson is a disallowed deserializer within Microsoft, signing off on .NET 10 will require us to attest that every call site into rapidjson processes only fully trusted input.

This is straightforward enough for a point-in-time assessment, but it does represent ongoing cost, and we don't want to risk introducing new call sites into this logic where we can't guarantee that the input is trustworthy. To facilitate this, I recommend renaming our rapidjson wrapping utility methods to parse_fully_trusted_raw_data and parse_fully_trusted_file, which clearly indicate at the invocation site that the caller passes only fully trustworthy data. This should reduce the risk of us violating the trust contract going forward and should simplify future attestations.

Additionally, we received internal bug reports of in situ parsing causing AVs when parsing improperly formatted JSON payloads. rapidjson's in situ parser requires a mutable null-terminated string; and since we use mmap files, we can't guarantee the presence of a null terminator. This means that if the file length happens to exactly match the page size of the underlying OS, and if the underlying JSON blob has an error (like missing the closing bracket), rapidjson will try to read into unmapped memory and crash the process rather than report an actionable error.

This is resolved by passing an explicit length to the parse routine. However, since ParseInsitu doesn't provide an overload that takes an explicit length, we should fall back to normal parsing.

Note to reviewers: If we really do want to enable in-situ parsing, we could always create our own GenericInsituStringStream-like type which takes a length and call ParseStream, passing kParseInsituFlag as a template arg. We can't use MemoryStream directly since it's read-only.

(Jeff, this is what I pinged you about via IM.)

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review any files in this pull request.

Files not reviewed (7)
  • src/native/corehost/comhost/clsidmap.cpp: Language not supported
  • src/native/corehost/fxr/sdk_resolver.cpp: Language not supported
  • src/native/corehost/fxr/standalone/hostpolicy_resolver.cpp: Language not supported
  • src/native/corehost/hostpolicy/deps_format.cpp: Language not supported
  • src/native/corehost/json_parser.cpp: Language not supported
  • src/native/corehost/json_parser.h: Language not supported
  • src/native/corehost/runtime_config.cpp: Language not supported

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @vitek-karas, @agocke, @VSadov
See info in area-owners.md if you want to be subscribed.

@GrabYourPitchforks

Copy link
Copy Markdown
MemberAuthor

Rebased on latest main to resolve merge conflicts.

CopilotAI review requested due to automatic review settings March 24, 2026 19:01
@GrabYourPitchforksGrabYourPitchforks changed the title Rename rapidjson helper functionsRename rapidjson helper functions; remove in situ parsingMar 24, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (4)

src/native/corehost/json_parser.cpp:114

  • In parse_fully_trusted_file, m_data = (char*)pal::mmap_read(...) casts away const from a read-only mapping. Even though current parsing is non-mutating, this makes it easy to accidentally introduce writes later (which would fault on Unix/Windows). Prefer keeping the mapping pointer const throughout and only using mutable mappings when truly required.
 if (m_data == nullptr)
{
m_data = (char*)pal::mmap_read(path, &m_size);
if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;
}

src/native/corehost/json_parser.cpp:113

  • The error text "Cannot use file stream for [...]" is misleading here since the code path is attempting a memory-map (mmap_read), not a file stream. Consider updating the message to reflect the actual operation (mapping the file) to make diagnostics clearer.
 if (m_data == nullptr)
{
trace::error(_X("Cannot use file stream for [%s]: %s"), path.c_str(), pal::strerror(errno).c_str());
return false;

src/native/corehost/json_parser.cpp:36

  • get_line_column_from_offset can read past the end of the buffer: when i == size - 1 (possible when offset == size), the data[i + 1] access in the CRLF check is out-of-bounds. Add a bounds check (e.g., ensure i + 1 < size) before reading data[i + 1] so malformed/edge inputs don’t trigger an OOB read while formatting parse errors.
void get_line_column_from_offset(const char* data, size_t size, size_t offset, int *line, int *column)
{
assert(offset <= size);
*line = *column = 1;
for (size_t i = 0; i < offset; i++)
{
(*column)++;
if (data[i] == '\n')
{
(*line)++;
*column = 1;
}
else if (data[i] == '\r' && data[i + 1] == '\n')
{

src/native/corehost/json_parser.h:52

  • pal::mmap_read returns a const void* (read-only mapping), but m_data is a mutable char* and the code casts away const when assigning the mapping. Since parsing no longer uses in-situ mutation, consider making m_data a const char* and updating parse_fully_trusted_raw_data to take const char* to avoid misleading mutability and accidental writes to a read-only mapping.
 bool parse_fully_trusted_raw_data(char* data, size_t size, const pal::string_t& context);
bool parse_fully_trusted_file(const pal::string_t& path);
json_parser_t()
: m_data(nullptr)
, m_bundle_location(nullptr) {}
~json_parser_t();
private:
char* m_data; // The memory mapped bytes of the file
size_t m_size; // Size of the mapped memory

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/native/corehost/json_parser.cpp:40

  • In get_line_column_from_offset, the CRLF detection now checks (i + 1) < offset instead of guarding against the end of the buffer. This can under-count newlines when the error offset points at the \n of a CRLF pair, and it’s not necessary for bounds safety (the real bound is size). Consider changing the condition to guard with size so CRLF is recognized when present while still avoiding an out-of-bounds read.
 else if (data[i] == '\r' && (i + 1) < offset && data[i + 1] == '\n')
{
(*line)++;
*column = 1;
i++; // Discard carriage return

Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@pavelsavara

pavelsavara commented Mar 25, 2026

Copy link
Copy Markdown
Member

I will comment from browser/wasm perspective.

  • I believe that main use-case for json parser in coreCLR codebase is to parse runtimeconfig.json
  • in browser the host is statically linked with the runtime, the the part that chooses runtime version doesn't apply.
  • so we only need to know values of configProperties and that's is (string,string)[] list of pair of strings.
  • I think it's like that for all "mobile" targets.
  • in browserhost for CoreCLR we use browser json parser to parse much larger manifest, and configProperties are part of that. The JS side will transform that directly into appctx_keys, appctx_values C UTF8 char**
  • in mono for browser it's also like that

for(const[key,value]ofruntimeConfigProperties.entries()){
constkeyPtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(key);
constvaluePtr=_ems_.dotnetBrowserUtilsExports.stringToUTF8Ptr(value);
_ems_.dotnetApi.setHeapU32((appctx_keysasany)+(propertyCount*sizeOfPtr),keyPtr);
_ems_.dotnetApi.setHeapU32((appctx_valuesasany)+(propertyCount*sizeOfPtr),valuePtr);
propertyCount++;
buffers.push(keyPtrasany);
buffers.push(valuePtrasany);
}

construntimeConfigProperties=newMap<string,string>();
if(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties){
for(const[key,value]ofObject.entries(runtimeHelpers.config.runtimeConfig?.runtimeOptions?.configProperties)){
runtimeConfigProperties.set(key,""+value);
}
}
runtimeConfigProperties.set("APP_CONTEXT_BASE_DIRECTORY","/");
runtimeConfigProperties.set("RUNTIME_IDENTIFIER","browser-wasm");
constpropertyCount=runtimeConfigProperties.size;
constbuffers:VoidPtr[]=[];
constappctx_keys=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;
constappctx_values=malloc(4*runtimeConfigProperties.size)asanyasCharPtrPtr;

In the past we avoided need for json parser in the VM, by converting into very simple binary file at compile time.
For browser I got rid of that in #115113

But I still see the same idea on apple/android

if(BundledRuntimeConfig?.ItemSpec!=null)
{
dataSymbol=BundledRuntimeConfig.GetMetadata("DataSymbol");
if(string.IsNullOrEmpty(dataSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataSymbol' metadata.");
}
dataLenSymbol=BundledRuntimeConfig.GetMetadata("DataLenSymbol");
if(string.IsNullOrEmpty(dataLenSymbol))
{
thrownewLogAsErrorException($"'{nameof(BundledRuntimeConfig)}' does not contain 'DataLenSymbol' metadata.");
}
externBundledResourcesSymbols.AppendLine();
externBundledResourcesSymbols.AppendLine($"extern uint8_t {dataSymbol}[];");
externBundledResourcesSymbols.AppendLine($"extern const uint32_t {dataLenSymbol};");
}

In the future, we may still need this compile time trick for WASI, but just for the configProperties part.

So my angle is, let's delete it, it makes coreCLR binary larger download for the browser.
(I realize normal OS story is different)

@jkotas

Copy link
Copy Markdown
Member

it makes coreCLR binary larger download for the browser.

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

@pavelsavara

Copy link
Copy Markdown
Member

This parser should not be linked into coreclr binary, on any OS. Do you see it compiled into coreclr binary on browser?

host and vm are the same binary for wasm, but I stand corrected, we are NOT building/linking it in the browser.

if(NOT CLR_CMAKE_TARGET_BROWSER)
add_library(fxr_resolverINTERFACE)
target_sources(fxr_resolverINTERFACEfxr_resolver.cpp)
target_include_directories(fxr_resolverINTERFACEfxr)
add_compile_definitions(RAPIDJSON_HAS_CXX17)
if ((NOTDEFINED CLR_CMAKE_USE_SYSTEM_RAPIDJSON) OR (NOT CLR_CMAKE_USE_SYSTEM_RAPIDJSON))
include_directories(${CLR_SRC_NATIVE_DIR}/external/)
endif()

I don't know about other single-file hosts.

@GrabYourPitchforks

GrabYourPitchforks commented Mar 27, 2026

Copy link
Copy Markdown
MemberAuthor

Elinor requested offline that I run the startup perf benchmarks. Should get to that next week.

Edit: No measurable change. 60 ~ 62 ms on my machine to launch emptycsconsoletemplate, regardless of whether the runtime comes from main or from this PR's branch. I also confirmed the perf scaffolding drops a runtimeconfig.json file alongside the executable, which I tweaked to target net11:

{
"runtimeOptions": {
"tfm": "net11.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "11.0.0"
},
"configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false,
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false
}
}
}

CopilotAI review requested due to automatic review settings July 7, 2026 23:51

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment threadsrc/native/corehost/json_parser.h
Comment threadsrc/native/corehost/json_parser.cpp
Comment threadsrc/native/corehost/json_parser.cpp
@GrabYourPitchforks
GrabYourPitchforks merged commit 0f9f04b into dotnet:mainJul 8, 2026
172 checks passed
@dotnet-milestone-botdotnet-milestone-botBot added this to the 11.0-preview7 milestone Jul 10, 2026
@am11am11 mentioned this pull request Jul 24, 2026
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Aug 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@GrabYourPitchforks@pavelsavara@jkotas@elinor-fung@karelz