Add verbatim builtin.

This commit is contained in:
chriseth 2021-03-17 19:37:39 +01:00
parent 2969bc0f3e
commit e2d8005737
34 changed files with 371 additions and 13 deletions

View File

@ -1,7 +1,8 @@
### 0.8.5 (unreleased)
Language Features:
* Allowing conversion from ``bytes`` and ``bytes`` slices to ``bytes1``/.../``bytes32``.
* Allowing conversion from ``bytes`` and ``bytes`` slices to ``bytes1``/.../``bytes32``.
* Yul: Add ``verbatim`` builtin function to inject arbitrary bytecode.
Compiler Features:

View File

@ -976,6 +976,84 @@ within one Yul subobject. If at least one ``memoryguard`` call is found in a sub
the additional optimiser steps will be run on it.
verbatim
^^^^^^^^
The set of ``verbatim...`` builtin functions lets you create bytecode for opcodes
that are not known to the Yul compiler. It also allows you to create
bytecode sequences that will not be modified by the optimizer.
The functions are ``verbatim_<n>i_<m>o("<data>", ...)``, where
- ``n`` is a decimal between 0 and 99 that specifies the number of input stack slots / variables
- ``m`` is a decimal between 0 and 99 that specifies the number of output stack slots / variables
- ``data`` is a string literal that contains the sequence of bytes
If you for example want to define a function that multiplies the input
by two, without the optimizer touching the constant two, you can use
.. code-block:: yul
let x := calldataload(0)
let double := verbatim_1i_1o(hex"600202", x)
This code will result in a ``dup1`` opcode to retrieve ``x``
(the optimizer might directly re-use result of the
``calldataload`` opcode, though)
directly followed by ``600202``. The code is assumed to
consume the copied value of ``x`` and produce the result
on the top of the stack. The compiler then generates code
to allocate a stack slot for ``double`` and store the result there.
As with all opcodes, the arguments are arranged on the stack
with the leftmost argument on the top, while the return values
are assumed to be laid out such that the rightmost variable is
at the top of the stack.
Since ``verbatim`` can be used to generate arbitrary opcodes
or even opcodes unknown to the Solidity compiler, care has to be taken
when using ``verbatim`` together with the optimizer. Even when the
optimizer is switched off, the code generator has to determine
the stack layout, which means that e.g. using ``verbatim`` to modify
the stack height can lead to undefined behaviour.
The following is a non-exhaustive list of restrictions on
verbatim bytecode that are not checked by
the compiler. Violations of these restrictions can result in
undefined behaviour.
- Control-flow should not jump into or out of verbatim blocks,
but it can jump within the same verbatim block.
- Stack contents apart from the input and output parameters
should not be accessed.
- The stack height difference should be exactly ``m - n``
(output slots minus input slots).
- Verbatim bytecode cannot make any assumptions about the
surrounding bytecode. All required parameters have to be
passed in as stack variables.
The optimizer does not analyze verbatim bytecode and always
assumes that it modifies all aspects of state and thus can only
do very few optimizations across ``verbatim`` function calls.
The optimizer treats verbatim bytecode as an opaque block of code.
It will not split it but might move, duplicate
or combine it with identical verbatim bytecode blocks.
If a verbatim bytecode block is unreachable by the control-flow,
it can be removed.
.. warning::
During discussions about whether or not EVM improvements
might break existing smart contracts, features inside ``verbatim``
cannot receive the same consideration as those used by the Solidity
compiler itself.
.. note::
To avoid confusion, all identifiers starting with the string ``verbatim`` are reserved
and cannot be used for user-defined identifiers.
.. _yul-object:
Specification of Yul Object

View File

@ -33,9 +33,11 @@
#include <liblangutil/Exceptions.h>
#include <fstream>
#include <json/json.h>
#include <fstream>
#include <range/v3/algorithm/any_of.hpp>
using namespace std;
using namespace solidity;
using namespace solidity::evmasm;
@ -317,6 +319,9 @@ Json::Value Assembly::assemblyJSON(map<string, unsigned> const& _sourceIndices)
case PushData:
collection.append(createJsonValue("PUSH data", sourceIndex, i.location().start, i.location().end, toStringInHex(i.data())));
break;
case VerbatimBytecode:
collection.append(createJsonValue("VERBATIM", sourceIndex, i.location().start, i.location().end, toHex(i.verbatimData())));
break;
default:
assertThrow(false, InvalidOpcode, "");
}
@ -482,7 +487,9 @@ map<u256, u256> Assembly::optimiseInternal(
// function types that can be stored in storage.
AssemblyItems optimisedItems;
bool usesMSize = (find(m_items.begin(), m_items.end(), AssemblyItem{Instruction::MSIZE}) != m_items.end());
bool usesMSize = ranges::any_of(m_items, [](AssemblyItem const& _i) {
return _i == AssemblyItem{Instruction::MSIZE} || _i.type() == VerbatimBytecode;
});
auto iter = m_items.begin();
while (iter != m_items.end())
@ -682,6 +689,9 @@ LinkerObject const& Assembly::assemble() const
ret.immutableReferences[i.data()].second.emplace_back(ret.bytecode.size());
ret.bytecode.resize(ret.bytecode.size() + 32);
break;
case VerbatimBytecode:
ret.bytecode += i.verbatimData();
break;
case AssignImmutable:
{
auto const& offsets = immutableReferencesBySub[i.data()].second;

View File

@ -73,6 +73,8 @@ public:
void appendImmutable(std::string const& _identifier) { append(newPushImmutable(_identifier)); }
void appendImmutableAssignment(std::string const& _identifier) { append(newImmutableAssignment(_identifier)); }
void appendVerbatim(bytes const& _data, int _stackDifference) { append(AssemblyItem(_data, _stackDifference)); }
AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; }
AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; }
AssemblyItem appendJump(AssemblyItem const& _tag) { auto ret = append(_tag.pushTag()); append(Instruction::JUMP); return ret; }

View File

@ -91,6 +91,8 @@ size_t AssemblyItem::bytesRequired(size_t _addressLength) const
return 1 + (3 + 32) * *m_immutableOccurrences;
else
return 1 + (3 + 32) * 1024; // 1024 occurrences are beyond the maximum code size anyways.
case VerbatimBytecode:
return m_verbatimBytecode->second.size();
default:
break;
}
@ -241,6 +243,9 @@ string AssemblyItem::toAssemblyText(Assembly const& _assembly) const
case UndefinedItem:
assertThrow(false, AssemblyException, "Invalid assembly item.");
break;
case VerbatimBytecode:
text = string("verbatimbytecode_") + util::toHex(m_verbatimBytecode->second);
break;
default:
assertThrow(false, InvalidOpcode, "");
}
@ -309,6 +314,9 @@ ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item)
case AssignImmutable:
_out << " AssignImmutable";
break;
case VerbatimBytecode:
_out << " Verbatim " << util::toHex(_item.verbatimData());
break;
case UndefinedItem:
_out << " ???";
break;

View File

@ -33,7 +33,8 @@
namespace solidity::evmasm
{
enum AssemblyItemType {
enum AssemblyItemType
{
UndefinedItem,
Operation,
Push,
@ -47,7 +48,8 @@ enum AssemblyItemType {
PushLibraryAddress, ///< Push a currently unknown address of another (library) contract.
PushDeployTimeAddress, ///< Push an address to be filled at deploy time. Should not be touched by the optimizer.
PushImmutable, ///< Push the currently unknown value of an immutable variable. The actual value will be filled in by the constructor.
AssignImmutable ///< Assigns the current value on the stack to an immutable variable. Only valid during creation code.
AssignImmutable, ///< Assigns the current value on the stack to an immutable variable. Only valid during creation code.
VerbatimBytecode ///< Contains data that is inserted into the bytecode code section without modification.
};
class Assembly;
@ -75,6 +77,12 @@ public:
else
m_data = std::make_shared<u256>(std::move(_data));
}
explicit AssemblyItem(bytes const& _verbatimData, int _deposit):
m_type(VerbatimBytecode),
m_instruction{},
m_verbatimBytecode{{_deposit, _verbatimData}}
{}
AssemblyItem(AssemblyItem const&) = default;
AssemblyItem(AssemblyItem&&) = default;
AssemblyItem& operator=(AssemblyItem const&) = default;
@ -95,6 +103,8 @@ public:
u256 const& data() const { assertThrow(m_type != Operation, util::Exception, ""); return *m_data; }
void setData(u256 const& _data) { assertThrow(m_type != Operation, util::Exception, ""); m_data = std::make_shared<u256>(_data); }
bytes const& verbatimData() const { assertThrow(m_type == VerbatimBytecode, util::Exception, ""); return m_verbatimBytecode->second; }
/// @returns the instruction of this item (only valid if type() == Operation)
Instruction instruction() const { assertThrow(m_type == Operation, util::Exception, ""); return m_instruction; }
@ -105,6 +115,8 @@ public:
return false;
if (type() == Operation)
return instruction() == _other.instruction();
else if (type() == VerbatimBytecode)
return *m_verbatimBytecode == *_other.m_verbatimBytecode;
else
return data() == _other.data();
}
@ -116,6 +128,8 @@ public:
return type() < _other.type();
else if (type() == Operation)
return instruction() < _other.instruction();
else if (type() == VerbatimBytecode)
return *m_verbatimBytecode == *_other.m_verbatimBytecode;
else
return data() < _other.data();
}
@ -137,7 +151,7 @@ public:
size_t bytesRequired(size_t _addressLength) const;
size_t arguments() const;
size_t returnValues() const;
size_t deposit() const { return returnValues() - arguments(); }
size_t deposit() const { return m_type == VerbatimBytecode ? static_cast<size_t>(m_verbatimBytecode->first) : returnValues() - arguments(); }
/// @returns true if the assembly item can be used in a functional context.
bool canBeFunctional() const;
@ -162,6 +176,8 @@ private:
AssemblyItemType m_type;
Instruction m_instruction; ///< Only valid if m_type == Operation
std::shared_ptr<u256> m_data; ///< Only valid if m_type != Operation
/// If m_type == VerbatimBytecode, this holds stack difference and verbatim bytecode.
std::optional<std::pair<int, bytes>> m_verbatimBytecode;
langutil::SourceLocation m_location;
JumpType m_jumpType = JumpType::Ordinary;
/// Pushed value for operations with data to be determined during assembly stage,

View File

@ -38,6 +38,7 @@ bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item, bool
case Tag:
case PushDeployTimeAddress:
case AssignImmutable:
case VerbatimBytecode:
return true;
case Push:
case PushString:
@ -164,6 +165,8 @@ bool SemanticInformation::reverts(Instruction _instruction)
bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
{
assertThrow(_item.type() != VerbatimBytecode, AssemblyException, "");
if (_item.type() != Operation)
return true;

View File

@ -369,16 +369,26 @@ vector<YulString> AsmAnalyzer::operator()(FunctionCall const& _funCall)
);
else if (*literalArgumentKind == LiteralKind::String)
{
if (
_funCall.functionName.name.str() == "datasize" ||
_funCall.functionName.name.str() == "dataoffset"
)
string functionName = _funCall.functionName.name.str();
if (functionName == "datasize" || functionName == "dataoffset")
{
if (!m_dataNames.count(get<Literal>(arg).value))
m_errorReporter.typeError(
3517_error,
get<Literal>(arg).location,
"Unknown data object \"" + std::get<Literal>(arg).value.str() + "\"."
);
}
else if (functionName.substr(0, "verbatim_"s.size()) == "verbatim_")
{
if (get<Literal>(arg).value.empty())
m_errorReporter.typeError(
1844_error,
get<Literal>(arg).location,
"The \"verbatim_*\" builtins cannot be used with empty bytecode."
);
}
argTypes.emplace_back(expectUnlimitedStringLiteral(get<Literal>(arg)));
continue;
}

View File

@ -44,7 +44,7 @@ struct BuiltinFunction
std::vector<Type> returns;
SideEffects sideEffects;
ControlFlowSideEffects controlFlowSideEffects;
/// If true, this is the msize instruction.
/// If true, this is the msize instruction or might contain it.
bool isMSize = false;
/// Must be empty or the same length as the arguments.
/// If set at index i, the i'th argument has to be a literal which means it can't be moved to variables.

View File

@ -77,6 +77,9 @@ public:
/// Currently, we assume that the value is always a 20 byte number.
virtual void appendLinkerSymbol(std::string const& _name) = 0;
/// Append raw bytes that stay untouched by the optimizer.
virtual void appendVerbatim(bytes const& _data, int _stackDifference) = 0;
/// Append a jump instruction.
/// @param _stackDiffAfter the stack adjustment after this instruction.
/// This is helpful to stack height analysis if there is no continuing control flow.

View File

@ -99,6 +99,11 @@ void EthAssemblyAdapter::appendLinkerSymbol(std::string const& _linkerSymbol)
m_assembly.appendLibraryAddress(_linkerSymbol);
}
void EthAssemblyAdapter::appendVerbatim(bytes const& _data, int _stackDifference)
{
m_assembly.appendVerbatim(_data, _stackDifference);
}
void EthAssemblyAdapter::appendJump(int _stackDiffAfter, JumpType _jumpType)
{
appendJumpInstruction(evmasm::Instruction::JUMP, _jumpType);

View File

@ -50,6 +50,7 @@ public:
size_t newLabelId() override;
size_t namedLabel(std::string const& _name) override;
void appendLinkerSymbol(std::string const& _linkerSymbol) override;
void appendVerbatim(bytes const& _data, int _stackDifference) override;
void appendJump(int _stackDiffAfter, JumpType _jumpType) override;
void appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) override;
void appendJumpToIf(LabelID _labelId, JumpType _jumpType) override;

View File

@ -34,6 +34,9 @@
#include <liblangutil/Exceptions.h>
#include <range/v3/view/reverse.hpp>
#include <range/v3/view/tail.hpp>
#include <regex>
using namespace std;
using namespace solidity;
@ -280,6 +283,12 @@ map<YulString, BuiltinFunctionForEVM> createBuiltins(langutil::EVMVersion _evmVe
return builtins;
}
regex const& verbatimPattern()
{
regex static const pattern{"verbatim_([1-9]?[0-9])i_([1-9]?[0-9])o"};
return pattern;
}
}
@ -293,6 +302,12 @@ EVMDialect::EVMDialect(langutil::EVMVersion _evmVersion, bool _objectAccess):
BuiltinFunctionForEVM const* EVMDialect::builtin(YulString _name) const
{
if (m_objectAccess)
{
smatch match;
if (regex_match(_name.str(), match, verbatimPattern()))
return verbatimFunction(stoul(match[1]), stoul(match[2]));
}
auto it = m_functions.find(_name);
if (it != m_functions.end())
return &it->second;
@ -302,6 +317,9 @@ BuiltinFunctionForEVM const* EVMDialect::builtin(YulString _name) const
bool EVMDialect::reservedIdentifier(YulString _name) const
{
if (m_objectAccess)
if (_name.str().substr(0, "verbatim"s.size()) == "verbatim")
return true;
return m_reserved.count(_name) != 0;
}
@ -342,6 +360,38 @@ SideEffects EVMDialect::sideEffectsOfInstruction(evmasm::Instruction _instructio
};
}
BuiltinFunctionForEVM const* EVMDialect::verbatimFunction(size_t _arguments, size_t _returnVariables) const
{
pair<size_t, size_t> key{_arguments, _returnVariables};
shared_ptr<BuiltinFunctionForEVM const>& function = m_verbatimFunctions[key];
if (!function)
{
BuiltinFunctionForEVM builtinFunction = createFunction(
"verbatim_" + to_string(_arguments) + "i_" + to_string(_returnVariables) + "o",
1 + _arguments,
_returnVariables,
SideEffects::worst(),
vector<optional<LiteralKind>>{LiteralKind::String} + vector<optional<LiteralKind>>(_arguments),
[=](
FunctionCall const& _call,
AbstractAssembly& _assembly,
BuiltinContext&,
std::function<void(Expression const&)> _visitExpression
) {
yulAssert(_call.arguments.size() == (1 + _arguments), "");
for (Expression const& arg: _call.arguments | ranges::views::tail | ranges::views::reverse)
_visitExpression(arg);
Expression const& bytecode = _call.arguments.front();
int diff = static_cast<int>(_returnVariables) - static_cast<int>(_arguments);
_assembly.appendVerbatim(asBytes(std::get<Literal>(bytecode).value.str()), diff);
}
).second;
builtinFunction.isMSize = true;
function = make_shared<BuiltinFunctionForEVM const>(move(builtinFunction));
}
return function.get();
}
EVMDialectTyped::EVMDialectTyped(langutil::EVMVersion _evmVersion, bool _objectAccess):
EVMDialect(_evmVersion, _objectAccess)
{

View File

@ -93,9 +93,12 @@ struct EVMDialect: public Dialect
static SideEffects sideEffectsOfInstruction(evmasm::Instruction _instruction);
protected:
BuiltinFunctionForEVM const* verbatimFunction(size_t _arguments, size_t _returnVariables) const;
bool const m_objectAccess;
langutil::EVMVersion const m_evmVersion;
std::map<YulString, BuiltinFunctionForEVM> m_functions;
std::map<std::pair<size_t, size_t>, std::shared_ptr<BuiltinFunctionForEVM const>> mutable m_verbatimFunctions;
std::set<YulString> m_reserved;
};

View File

@ -69,6 +69,11 @@ void NoOutputAssembly::appendLinkerSymbol(string const&)
yulAssert(false, "Linker symbols not yet implemented.");
}
void NoOutputAssembly::appendVerbatim(bytes const&, int _stackDifference)
{
m_stackHeight += _stackDifference;
}
void NoOutputAssembly::appendJump(int _stackDiffAfter, JumpType)
{
appendInstruction(evmasm::Instruction::JUMP);

View File

@ -58,6 +58,7 @@ public:
LabelID newLabelId() override;
LabelID namedLabel(std::string const& _name) override;
void appendLinkerSymbol(std::string const& _name) override;
void appendVerbatim(bytes const& _data, int _stackDifference) override;
void appendJump(int _stackDiffAfter, JumpType _jumpType) override;
void appendJumpTo(LabelID _labelId, int _stackDiffAfter, JumpType _jumpType) override;

View File

@ -131,7 +131,8 @@ public:
};
/**
* Class that can be used to find out if certain code contains the MSize instruction.
* Class that can be used to find out if certain code contains the MSize instruction
* or a verbatim bytecode builtin (which is always assumed that it could contain MSize).
*
* Note that this is a purely syntactic property meaning that even if this is false,
* the code can still contain calls to functions that contain the msize instruction.

View File

@ -0,0 +1 @@
--strict-assembly

View File

@ -0,0 +1 @@
Warning: Yul is still experimental. Please use the output with care.

View File

@ -0,0 +1 @@
0

View File

@ -0,0 +1,11 @@
{
let x := 2
let y := sub(x, 2)
let t := verbatim_2i_1o("abc", x, y)
sstore(t, x)
let r := verbatim_0i_1o("def")
verbatim_0i_0o("xyz")
// more than 32 bytes
verbatim_0i_0o(hex"01020304050607090001020304050607090001020304050607090001020102030405060709000102030405060709000102030405060709000102")
r := 9
}

View File

@ -0,0 +1,56 @@
======= yul_verbatim/input.yul (EVM) =======
Pretty printed source:
object "object" {
code {
let x := 2
let y := sub(x, 2)
let t := verbatim_2i_1o("abc", x, y)
sstore(t, x)
let r := verbatim_0i_1o("def")
verbatim_0i_0o("xyz")
verbatim_0i_0o("\x01\x02\x03\x04\x05\x06\x07\t\x00\x01\x02\x03\x04\x05\x06\x07\t\x00\x01\x02\x03\x04\x05\x06\x07\t\x00\x01\x02\x01\x02\x03\x04\x05\x06\x07\t\x00\x01\x02\x03\x04\x05\x06\x07\t\x00\x01\x02\x03\x04\x05\x06\x07\t\x00\x01\x02")
r := 9
}
}
Binary representation:
600260028103808261626382815564656678797a010203040506070900010203040506070900010203040506070900010201020304050607090001020304050607090001020304050607090001026009905050505050
Text representation:
/* "yul_verbatim/input.yul":15:16 */
0x02
/* "yul_verbatim/input.yul":37:38 */
0x02
/* "yul_verbatim/input.yul":34:35 */
dup2
/* "yul_verbatim/input.yul":30:39 */
sub
/* "yul_verbatim/input.yul":78:79 */
dup1
/* "yul_verbatim/input.yul":75:76 */
dup3
verbatimbytecode_616263
/* "yul_verbatim/input.yul":95:96 */
dup3
/* "yul_verbatim/input.yul":92:93 */
dup2
/* "yul_verbatim/input.yul":85:97 */
sstore
verbatimbytecode_646566
/* "yul_verbatim/input.yul":137:158 */
verbatimbytecode_78797a
/* "yul_verbatim/input.yul":189:326 */
verbatimbytecode_01020304050607090001020304050607090001020304050607090001020102030405060709000102030405060709000102030405060709000102
/* "yul_verbatim/input.yul":336:337 */
0x09
/* "yul_verbatim/input.yul":331:337 */
swap1
pop
/* "yul_verbatim/input.yul":0:339 */
pop
pop
pop
pop

View File

@ -0,0 +1 @@
--strict-assembly --optimize

View File

@ -0,0 +1 @@
Warning: Yul is still experimental. Please use the output with care.

View File

@ -0,0 +1 @@
0

View File

@ -0,0 +1,7 @@
{
// The optimizer assumes verbatim could contain msize,
// so it cannot optimize the mload away.
let x := mload(0x2000)
verbatim_0i_0o("")
sstore(0, 2)
}

View File

@ -0,0 +1,32 @@
======= yul_verbatim_msize/input.yul (EVM) =======
Pretty printed source:
object "object" {
code {
{
pop(mload(0x2000))
verbatim_0i_0o("aa")
sstore(0, 2)
}
}
}
Binary representation:
612000515061616002600055
Text representation:
/* "yul_verbatim_msize/input.yul":125:131 */
0x2000
/* "yul_verbatim_msize/input.yul":119:132 */
mload
pop
/* "yul_verbatim_msize/input.yul":137:157 */
verbatimbytecode_6161
/* "yul_verbatim_msize/input.yul":172:173 */
0x02
/* "yul_verbatim_msize/input.yul":169:170 */
0x00
/* "yul_verbatim_msize/input.yul":162:174 */
sstore

View File

@ -33,6 +33,8 @@
#include <boost/test/unit_test.hpp>
#include <range/v3/algorithm/any_of.hpp>
#include <string>
#include <tuple>
#include <memory>
@ -67,7 +69,9 @@ namespace
{
AssemblyItems input = addDummyLocations(_input);
bool usesMsize = (find(_input.begin(), _input.end(), AssemblyItem{Instruction::MSIZE}) != _input.end());
bool usesMsize = ranges::any_of(_input, [](AssemblyItem const& _i) {
return _i == AssemblyItem{Instruction::MSIZE} || _i.type() == VerbatimBytecode;
});
evmasm::CommonSubexpressionEliminator cse(_state);
BOOST_REQUIRE(cse.feedItems(input.begin(), input.end(), usesMsize) == input.end());
AssemblyItems output = cse.getOptimizedItems();

View File

@ -0,0 +1,12 @@
contract C {
function f() pure public {
assembly {
let x := verbatim_1o(hex"6001")
}
}
}
// ====
// optimize-yul: true
// ----
// DeclarationError 4619: (84-95): Function "verbatim_1o" not found.
// DeclarationError 3812: (75-106): Variable count mismatch for declaration of "x": 1 variables and 0 values.

View File

@ -0,0 +1,7 @@
{
verbatim_0i_0o(hex"")
}
// ====
// dialect: evm
// ----
// TypeError 1844: (21-26): The "verbatim_*" builtins cannot be used with empty bytecode.

View File

@ -0,0 +1,6 @@
{
let x := verbatim_01i_1o(hex"aa", 0)
}
// ----
// DeclarationError 4619: (15-30): Function "verbatim_01i_1o" not found.
// DeclarationError 3812: (6-42): Variable count mismatch for declaration of "x": 1 variables and 0 values.

View File

@ -0,0 +1,6 @@
{
let verbatim := 2
let verbatim_1i_2o := 3
}
// ----
// ParserError 5568: (32-46): Cannot use builtin function name "verbatim_1i_2o" as identifier name.

View File

@ -0,0 +1,9 @@
{
let verbatim := 2
let verbatim_abc := 3
function verbatim_fun() {}
}
// ----
// DeclarationError 5017: (10-18): The identifier "verbatim" is reserved and can not be used.
// DeclarationError 5017: (32-44): The identifier "verbatim_abc" is reserved and can not be used.
// DeclarationError 5017: (54-80): The identifier "verbatim_fun" is reserved and can not be used.

View File

@ -0,0 +1,5 @@
{
let verbatim_01i_02o := 2
}
// ----
// DeclarationError 5017: (10-26): The identifier "verbatim_01i_02o" is reserved and can not be used.