Coverage Report

Created: 2026-08-14 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libevmasm/Assembly.h
Line
Count
Source
1
/*
2
  This file is part of solidity.
3
4
  solidity is free software: you can redistribute it and/or modify
5
  it under the terms of the GNU General Public License as published by
6
  the Free Software Foundation, either version 3 of the License, or
7
  (at your option) any later version.
8
9
  solidity is distributed in the hope that it will be useful,
10
  but WITHOUT ANY WARRANTY; without even the implied warranty of
11
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
  GNU General Public License for more details.
13
14
  You should have received a copy of the GNU General Public License
15
  along with solidity.  If not, see <http://www.gnu.org/licenses/>.
16
*/
17
// SPDX-License-Identifier: GPL-3.0
18
19
#pragma once
20
21
#include <libevmasm/Instruction.h>
22
#include <liblangutil/SourceLocation.h>
23
#include <libevmasm/AssemblyItem.h>
24
#include <libevmasm/LinkerObject.h>
25
#include <libevmasm/Exceptions.h>
26
#include <libevmasm/SubAssemblyID.h>
27
28
#include <liblangutil/DebugInfoSelection.h>
29
#include <liblangutil/EVMVersion.h>
30
31
#include <libsolutil/Common.h>
32
#include <libsolutil/Assertions.h>
33
#include <libsolutil/Keccak256.h>
34
#include <libsolutil/JSON.h>
35
36
#include <libsolidity/interface/OptimiserSettings.h>
37
38
#include <iostream>
39
#include <sstream>
40
#include <memory>
41
#include <map>
42
#include <utility>
43
44
namespace solidity::evmasm
45
{
46
47
using AssemblyPointer = std::shared_ptr<Assembly>;
48
49
class Assembly
50
{
51
  using TagRefs = std::map<size_t, std::pair<SubAssemblyID, size_t>>;
52
  using DataRefs = std::multimap<util::h256, unsigned>;
53
  using SubAssemblyRefs = std::multimap<SubAssemblyID, size_t>;
54
  using ProgramSizeRefs = std::vector<unsigned>;
55
  using LinkRef = std::pair<size_t, std::string>;
56
57
public:
58
  Assembly(langutil::EVMVersion _evmVersion, bool _creation, std::string _name):
59
74.4k
    m_evmVersion(_evmVersion),
60
74.4k
    m_creation(_creation),
61
74.4k
    m_name(std::move(_name))
62
74.4k
  {}
63
64
1.78M
  AssemblyItem newTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(Tag, m_usedTags++); }
65
398k
  AssemblyItem newPushTag() { assertThrow(m_usedTags < 0xffffffff, AssemblyException, ""); return AssemblyItem(PushTag, m_usedTags++); }
66
67
  /// Returns a tag identified by the given name. Creates it if it does not yet exist.
68
  AssemblyItem namedTag(std::string const& _name, size_t _params, size_t _returns, std::optional<uint64_t> _sourceID);
69
16.0k
  AssemblyItem newData(bytes const& _data) { util::h256 h(util::keccak256(util::asString(_data))); m_data[h] = _data; return AssemblyItem(PushData, h); }
70
333
  bytes const& data(util::h256 const& _i) const { return m_data.at(_i); }
71
21.8k
  AssemblyItem newSub(AssemblyPointer const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); }
72
0
  Assembly const& sub(SubAssemblyID const _sub) const { return *m_subs.at(_sub.asIndex()); }
73
9.77k
  Assembly& sub(SubAssemblyID const _sub) { return *m_subs.at(_sub.asIndex()); }
74
37.9k
  size_t numSubs() const { return m_subs.size(); }
75
21.7k
  AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); }
76
  AssemblyItem newPushLibraryAddress(std::string const& _identifier);
77
  AssemblyItem newPushImmutable(std::string const& _identifier);
78
  AssemblyItem newImmutableAssignment(std::string const& _identifier);
79
80
  AssemblyItem const& append(AssemblyItem _i);
81
11.6k
  AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); }
82
83
262k
  template <class T> Assembly& operator<<(T const& _d) { append(_d); return *this; }
solidity::evmasm::Assembly& solidity::evmasm::Assembly::operator<< <solidity::evmasm::AssemblyItem>(solidity::evmasm::AssemblyItem const&)
Line
Count
Source
83
262k
  template <class T> Assembly& operator<<(T const& _d) { append(_d); return *this; }
solidity::evmasm::Assembly& solidity::evmasm::Assembly::operator<< <boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<256ul, 256ul, (boost::multiprecision::cpp_integer_type)0, (boost::multiprecision::cpp_int_check_type)0, void>, (boost::multiprecision::expression_template_option)0> >(boost::multiprecision::number<boost::multiprecision::backends::cpp_int_backend<256ul, 256ul, (boost::multiprecision::cpp_integer_type)0, (boost::multiprecision::cpp_int_check_type)0, void>, (boost::multiprecision::expression_template_option)0> const&)
Line
Count
Source
83
333
  template <class T> Assembly& operator<<(T const& _d) { append(_d); return *this; }
84
85
  /// Pushes the final size of the current assembly itself. Use this when the code is modified
86
  /// after compilation and CODESIZE is not an option.
87
949
  void appendProgramSize() { append(AssemblyItem(PushProgramSize)); }
88
1.67k
  void appendLibraryAddress(std::string const& _identifier) { append(newPushLibraryAddress(_identifier)); }
89
283
  void appendImmutable(std::string const& _identifier) { append(newPushImmutable(_identifier)); }
90
490
  void appendImmutableAssignment(std::string const& _identifier) { append(newImmutableAssignment(_identifier)); }
91
92
  void appendVerbatim(bytes _data, size_t _arguments, size_t _returnVariables)
93
1.22k
  {
94
1.22k
    append(AssemblyItem(std::move(_data), _arguments, _returnVariables));
95
1.22k
  }
96
97
13.9k
  AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; }
98
174k
  AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; }
99
0
  AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMP); return ret; }
100
35.6k
  AssemblyItem appendJumpI(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMPI); return ret; }
101
102
  /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag)
103
  /// on the stack. @returns the pushsub assembly item.
104
189
  AssemblyItem appendSubroutine(AssemblyPointer const& _assembly) { auto sub = newSub(_assembly); append(newPushSubSize(size_t(sub.data()))); return sub; }
105
21.5k
  void pushSubroutineSize(SubAssemblyID _subRoutine) { append(newPushSubSize(_subRoutine.value)); }
106
  /// Pushes the offset of the subroutine.
107
18.8k
  void pushSubroutineOffset(SubAssemblyID _subRoutine) { append(AssemblyItem(PushSub, _subRoutine.value)); }
108
109
  /// Appends @a _data literally to the very end of the bytecode.
110
18.1k
  void appendToAuxiliaryData(bytes const& _data) { m_auxiliaryData += _data; }
111
112
27.4M
  int deposit() const { return m_deposit; }
113
1.37M
  void adjustDeposit(int _adjustment) { m_deposit += _adjustment; solAssert(m_deposit >= 0); }
114
1.63M
  void setDeposit(int _deposit) { m_deposit = _deposit; solAssert(m_deposit >= 0); }
115
29.8k
  std::string const& name() const { return m_name; }
116
117
  /// Changes the source location used for each appended item.
118
18.3M
  void setSourceLocation(langutil::SourceLocation const& _location) { m_currentSourceLocation = _location; }
119
49.1k
  langutil::SourceLocation const& currentSourceLocation() const { return m_currentSourceLocation; }
120
4.07M
  langutil::EVMVersion const& evmVersion() const { return m_evmVersion; }
121
122
  /// Assembles the assembly into bytecode. The assembly should not be modified after this call, since the assembled version is cached.
123
  LinkerObject const& assemble() const;
124
125
  struct OptimiserSettings
126
  {
127
    bool runInliner = false;
128
    bool runJumpdestRemover = false;
129
    bool runPeephole = false;
130
    bool runDeduplicate = false;
131
    bool runCSE = false;
132
    bool runConstantOptimiser = false;
133
134
    using ExecutionCount = frontend::OptimiserSettings::ExecutionCount;
135
    /// This specifies an estimate on how often each opcode in this assembly will be executed,
136
    /// i.e. use a small value to optimise for size and a large value to optimise for runtime gas usage.
137
    ExecutionCount expectedExecutionsPerDeployment = frontend::OptimiserSettings{}.expectedExecutionsPerDeployment;
138
139
    static OptimiserSettings translateSettings(frontend::OptimiserSettings const& _settings);
140
  };
141
142
  /// Modify and return the current assembly such that creation and execution gas usage
143
  /// is optimised according to the settings in @a _settings.
144
  Assembly& optimise(OptimiserSettings const& _settings);
145
146
  /// Create a text representation of the assembly.
147
  std::string assemblyString(
148
    langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
149
    StringMap const& _sourceCodes = StringMap()
150
  ) const;
151
  void assemblyStream(
152
    std::ostream& _out,
153
    langutil::DebugInfoSelection const& _debugInfoSelection = langutil::DebugInfoSelection::Default(),
154
    std::string const& _prefix = "",
155
    StringMap const& _sourceCodes = StringMap()
156
  ) const;
157
158
  /// Create a JSON representation of the assembly.
159
  Json assemblyJSON(std::map<std::string, unsigned> const& _sourceIndices, bool _includeSourceList = true) const;
160
161
  /// Constructs an @a Assembly from the serialized JSON representation.
162
  /// @param _json JSON object containing assembly in the format produced by assemblyJSON().
163
  /// @param _sourceList List of source files the assembly was built from. When the JSON represents
164
  ///     the root assembly, the function will read it from the 'sourceList' field and the parameter
165
  ///     must be empty. It is only used to pass the list down to recursive calls.
166
  /// @param _level Nesting level of the current assembly in the assembly tree. The root is
167
  ///     at level 0 and the value increases down the tree. Necessary to distinguish between creation
168
  ///     and deployed objects.
169
  /// @returns Created @a Assembly and the source list read from the 'sourceList' field of the root
170
  ///     assembly or an empty list (in recursive calls).
171
  static std::pair<std::shared_ptr<Assembly>, std::vector<std::string>> fromJSON(
172
    Json const& _json,
173
    std::vector<std::string> const& _sourceList = {},
174
    size_t _level = 0
175
  );
176
177
  /// Mark this assembly as invalid. Calling ``assemble`` on it will throw.
178
20.4k
  void markAsInvalid() { m_invalid = true; }
179
180
  std::vector<SubAssemblyID> decodeSubPath(SubAssemblyID _subObjectId) const;
181
  SubAssemblyID encodeSubPath(std::vector<SubAssemblyID> const& _subPath);
182
183
189k
  bool isCreation() const { return m_creation; }
184
185
67.0k
  AssemblyItems& items() { return m_items; }
186
25.4k
  AssemblyItems const& items() const { return m_items; }
187
188
protected:
189
  /// Does the same operations as @a optimise, but should only be applied to a sub and
190
  /// returns the replaced tags. Also takes an argument containing the tags of this assembly
191
  /// that are referenced in a super-assembly.
192
  std::map<u256, u256> const& optimiseInternal(OptimiserSettings const& _settings, std::set<size_t> _tagsReferencedFromOutside);
193
194
  /// Calculates approximate size of "pure" code without data.
195
  unsigned codeSize(unsigned subTagSize) const;
196
197
  /// Add all assembly items from given JSON array. This function imports the items by iterating through
198
  /// the code array. This method only works on clean Assembly objects that don't have any items defined yet.
199
  /// @param _json JSON array that contains assembly items (e.g. json['.code'])
200
  /// @param _sourceList List of source names.
201
  void importAssemblyItemsFromJSON(Json const& _code, std::vector<std::string> const& _sourceList);
202
203
  /// Creates an AssemblyItem from a given JSON representation.
204
  /// @param _json JSON object that consists a single assembly item
205
  /// @param _sourceList List of source names.
206
  /// @returns AssemblyItem of _json argument.
207
  AssemblyItem createAssemblyItemFromJSON(Json const& _json, std::vector<std::string> const& _sourceList);
208
209
private:
210
  bool m_invalid = false;
211
212
  Assembly const* subAssemblyById(SubAssemblyID _subId) const;
213
214
  void encodeAllPossibleSubPathsInAssemblyTree(std::vector<SubAssemblyID> _pathFromRoot = {}, std::vector<Assembly*> _assembliesOnPath = {});
215
216
  std::shared_ptr<std::string const> sharedSourceName(std::string const& _name) const;
217
218
  LinkerObject const& assembleLegacy() const;
219
220
  /// Assemble bytecode for AssemblyItem type.
221
  [[nodiscard]] bytes assembleOperation(AssemblyItem const& _item) const;
222
  [[nodiscard]] bytes assemblePush(AssemblyItem const& _item) const;
223
  [[nodiscard]] std::pair<bytes, Assembly::LinkRef> assemblePushLibraryAddress(AssemblyItem const& _item, size_t _pos) const;
224
  [[nodiscard]] bytes assembleVerbatimBytecode(AssemblyItem const& item) const;
225
  [[nodiscard]] bytes assemblePushDeployTimeAddress() const;
226
  [[nodiscard]] bytes assembleTag(AssemblyItem const& _item, size_t _pos, bool _addJumpDest) const;
227
228
protected:
229
  /// 0 is reserved for exception
230
  unsigned m_usedTags = 1;
231
232
  struct NamedTagInfo
233
  {
234
    size_t id;
235
    std::optional<size_t> sourceID;
236
    size_t params;
237
    size_t returns;
238
  };
239
240
  std::map<std::string, NamedTagInfo> m_namedTags;
241
  std::map<util::h256, bytes> m_data;
242
  /// Data that is appended to the very end of the contract.
243
  bytes m_auxiliaryData;
244
  std::vector<std::shared_ptr<Assembly>> m_subs;
245
  AssemblyItems m_items;
246
  std::map<util::h256, std::string> m_strings;
247
  std::map<util::h256, std::string> m_libraries; ///< Identifiers of libraries to be linked.
248
  std::map<util::h256, std::string> m_immutables; ///< Identifiers of immutables.
249
250
  /// Map from a vector representing a path to a particular sub assembly to sub assembly id.
251
  /// This map is used only for sub-assemblies which are not direct sub-assemblies (where path is having more than one value).
252
  /// Nested/indirect SubAssemblyIDs are assigned negative unsigned 64 bit integers, i.e., the `i`th indirect
253
  /// index corresponds to `std::numeric_limits<std::uint64_t>::max() - i` in contrast to direct indices which occupy
254
  /// the non-negative half of the index space.
255
  std::map<std::vector<SubAssemblyID>, SubAssemblyID> m_subPaths;
256
257
  /// Contains the tag replacements relevant for super-assemblies.
258
  /// If set, it means the optimizer has run and we will not run it again.
259
  std::optional<std::map<u256, u256>> m_tagReplacements;
260
261
  mutable LinkerObject m_assembledObject;
262
  mutable std::vector<size_t> m_tagPositionsInBytecode;
263
264
  langutil::EVMVersion m_evmVersion;
265
266
  int m_deposit = 0;
267
  /// True, if the assembly contains contract creation code.
268
  bool const m_creation = false;
269
  /// Internal name of the assembly object, only used with the Yul backend
270
  /// currently
271
  std::string m_name;
272
  langutil::SourceLocation m_currentSourceLocation;
273
274
  // FIXME: This being static means that the strings won't be freed when they're no longer needed
275
  static std::map<std::string, std::shared_ptr<std::string const>> s_sharedSourceNames;
276
277
public:
278
  size_t m_currentModifierDepth = 0;
279
};
280
281
inline std::ostream& operator<<(std::ostream& _out, Assembly const& _a)
282
0
{
283
0
  _a.assemblyStream(_out);
284
0
  return _out;
285
0
}
286
287
}