Skip to content

Disable implicit conversions from JSON values - #17121

Merged
clonker merged 1 commit into
argotorg:developfrom
walnuthq:build/json-explicit-conversions
Oct 19, 2026
Merged

clonker merged 1 commit into
argotorg:developfrom
walnuthq:build/json-explicit-conversions

Conversation

@djolertrk

Copy link
Copy Markdown
Contributor

Defines JSON_USE_IMPLICIT_CONVERSIONS=0 for the whole build. Converting a JSON value to a C++ type can fail, so it should be visible at the call site as an explicit get<T>().

The few implicit conversions this turned up are made explicit:

  • test/libevmasm/Assembler.cpp and test/libsolidity/StandardCompiler.cpp: five std::string initializations from JSON values.
  • StandardCompiler::parseInput(): the line ret.sources = Json::object(); assigned an empty JSON object to the std::map of a freshly constructed struct and did nothing, so it is removed.

The macro only makes JSON-to-C++ conversions explicit. Conversions from C++ types to JSON through to_json() remain implicit; nlohmann has no switch for those.

Suggested in #16990 (comment)...

@djolertrk

Copy link
Copy Markdown
Contributor Author

cc @cameel

@cameel
cameel requested review from aarlt, blishko and clonker and removed request for aarlt October 15, 2026 15:48
@blishko

blishko commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Is this uncovering some problem in emscripten? Looking at the CI failure of bytecode_compare job.

@djolertrk
djolertrk force-pushed the build/json-explicit-conversions branch from 7fadddc to 6a6783e Compare October 16, 2026 10:10
@djolertrk

Copy link
Copy Markdown
Contributor Author

Is this uncovering some problem in emscripten? Looking at the CI failure of bytecode_compare job.

well, it is not an emscripten problem, but I would say a real one uncovered by this change. The ABI entries are sorted by comparing tuples of JSON values, and tuples use operator<=>. Emscripten’s standard library has no three-way comparison, so nlohmann::json doesn’t define operator<=> there. Once implicit conversions are disabled, a <=> b resolves through the remaining implicit operator value_t() and compares only the JSON types, so the ABI was left unsorted. The native builds have a real operator<=> and aren’t affected. I changed the comparator to compare the type and name as strings, which gives the same order everywhere.

@blishko

blishko commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Interesting, thanks for analyzing the problem!
I would still like to understand it more. Do you think this would be solved also by updating emscripten version, with newer standard library?
Would your fix be desirable anyway?

@clonker

clonker commented Oct 16, 2026

Copy link
Copy Markdown
Member

Do you think this would be solved also by updating emscripten version, with newer standard library?

yes, emscripten >= 4.0.0 should do it. it comes with llvm 19.1.6. which defines __cpp_lib_three_way_comparison.

@djolertrk

Copy link
Copy Markdown
Contributor Author

Do you think this would be solved also by updating emscripten version, with newer standard library?

yes, emscripten >= 4.0.0 should do it. it comes with llvm 19.1.6. which defines __cpp_lib_three_way_comparison.

hmm it seems right, let me double check, thank you for the comment

@djolertrk

Copy link
Copy Markdown
Contributor Author

Do you think this would be solved also by updating emscripten version, with newer standard library?

yes, emscripten >= 4.0.0 should do it. it comes with llvm 19.1.6. which defines __cpp_lib_three_way_comparison.

hmm it seems right, let me double check, thank you for the comment

But, I’d still keep the fix, though. The old comparator only worked by accident of overload resolution, with implicit conversions on, a <=> b was ambiguous, so the tuple fell back to operator<. Whether it sorts correctly depends on standard library features, and when it doesn’t, nothing fails to compile and the output is just quietly wrong. Comparing the type and name as strings says what we mean and doesn’t depend on any of that. The order is the same as before in every configuration I checked.

Bumping emscripten seems worthwhile on its own, but I’d do it separately from this PR. wdyt?

@blishko

blishko commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

But, I’d still keep the fix, though.

I agree now.

Bumping emscripten seems worthwhile on its own, but I’d do it separately from this PR. wdyt?

Oh, definitely. Updating emscripten is a quest on its own. I have been working through the versions slowly, it's on pause at the moment.

@blishko blishko 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.

I now support this change.
However, I am not convinced we need the extra comment. Opinions?

Comment thread libsolidity/interface/ABI.cpp Outdated
Comment on lines +43 to +44
// Compare the keys as strings: tuples compare JSON values with operator<=>, which, without
// three-way comparison support in nlohmann::json, resolves to comparing only their types.

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.

I am not sure we need to mention the three-way comparison and its (lack of) support in nlohmann::json.
Such a comment can get outdated quickly.
Also, I am not sure what keys you mean here.

I am not sure we need a comment at all.
If anything, I would personally prefer the following:

// ABI entries are sorted based on their type and name.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd drop the comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah, I agree

@clonker clonker left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There is another site with the three-way comparison here

std::sort(externalReferences.begin(), externalReferences.end());

I think the better fix is to set JSON_HAS_THREE_WAY_COMPARISON=1 as compile flag and then update emscripten soon. I am fairly confident these are the only two sites but yeah, it's quite brittle to say the least. Any ordered container, sorting, and/or general comparison between json objects is potentially broken right now.

The test macro __cpp_lib_three_way_comparison that nlohmann json relies on is not set because of unrelated features not being ready yet (like chrono), the spaceship operator itself is available. To be extra safe we can lift the runtime fail to a compile time error:

static_assert(
    std::same_as<decltype(std::declval<Json const&>() <=>
    std::declval<Json const&>()), std::partial_ordering>
);

With emscripten >= 4.0.0 we can remove the define again if we agree to add it here.

@djolertrk

Copy link
Copy Markdown
Contributor Author

There is another site with the three-way comparison here

std::sort(externalReferences.begin(), externalReferences.end());

I think the better fix is to set JSON_HAS_THREE_WAY_COMPARISON=1 as compile flag and then update emscripten soon. I am fairly confident these are the only two sites but yeah, it's quite brittle to say the least. Any ordered container, sorting, and/or general comparison between json objects is potentially broken right now.

The test macro __cpp_lib_three_way_comparison that nlohmann json relies on is not set because of unrelated features not being ready yet (like chrono), the spaceship operator itself is available. To be extra safe we can lift the runtime fail to a compile time error:

static_assert(
    std::same_as<decltype(std::declval<Json const&>() <=>
    std::declval<Json const&>()), std::partial_ordering>
);

With emscripten >= 4.0.0 we can remove the define again if we agree to add it here.

Well, that’s a better fix. So this is addressed:

  • JSON_HAS_THREE_WAY_COMPARISON=1 is set next to JSON_USE_IMPLICIT_CONVERSIONS=0, with a TODO to remove it once we’re on emscripten >= 4.0.0.
  • The static_assert @clonker shared is in libsolutil/JSON.h, right after the Json alias. I checked it locally and it passes with the define and fails to compile without it. With implicit conversions on, it fails on the ambiguous <=>, and with them off, on the strong_ordering from value_t. That covers the ASTJsonExporter sort and any other ordered comparison of JSON values.
  • The ABI comparator change is reverted, since it isn’t needed anymore.

Converting a JSON value to another type can fail, so it should be
visible at the call site. With JSON_USE_IMPLICIT_CONVERSIONS=0 such
conversions need an explicit get<T>().

The one in StandardCompiler assigned an empty JSON object to a
std::map of a freshly constructed struct and did nothing, so it is
removed.

Tuples, pairs and containers of JSON values compare them with
operator<=>, which nlohmann::json only defines when the standard
library reports three-way comparison support. The standard library of
the emscripten build does not, and without implicit conversions <=>
then resolves through the conversion to value_t and compares only the
types of the values, which left the ABI unsorted there. Three-way
comparison is therefore enabled explicitly, and a static assertion
makes sure that <=> compares JSON values.
@djolertrk
djolertrk force-pushed the build/json-explicit-conversions branch from 6a6783e to 0bb88b0 Compare October 16, 2026 20:39
@clonker
clonker merged commit 4c8a81a into argotorg:develop Oct 19, 2026
83 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants