Commit 1a6cf58b authored by Clemens Hammacher's avatar Clemens Hammacher Committed by Commit Bot

[base] Move implicit_cast to macros.h

macros.h already not only defines macros, but also templatized helpers
like {bit_cast} and {arraysize}. Thus {implicit_cast} also belongs
there.

R=tebbi@chromium.org

Bug: v8:7570
Change-Id: Iaea6075dad359d62498453575f22d73ca84e2323
Reviewed-on: https://chromium-review.googlesource.com/1042401
Commit-Queue: Clemens Hammacher <clemensh@chromium.org>
Reviewed-by: 's avatarTobias Tebbi <tebbi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#52991}
parent bc218a2e
......@@ -43,7 +43,6 @@ template <typename T, size_t N>
char (&ArraySizeHelper(const T (&array)[N]))[N];
#endif
// bit_cast<Dest,Source> is a template function that implements the
// equivalent of "*reinterpret_cast<Dest*>(&source)". We need this in
// very low-level functions like the protobuf library and fast math
......@@ -271,6 +270,14 @@ struct Use {
} // namespace base
} // namespace v8
// implicit_cast<A>(x) triggers an implicit cast from {x} to type {A}. This is
// useful in situations where static_cast<A>(x) would do too much.
// Only use this for cheap-to-copy types, or use move semantics explicitly.
template <class A>
V8_INLINE A implicit_cast(A x) {
return x;
}
// Define our own macros for writing 64-bit constants. This is less fragile
// than defining __STDC_CONSTANT_MACROS before including <stdint.h>, and it
// works on compilers that don't have it (like MSVC).
......
......@@ -56,13 +56,6 @@ std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
// implicit_cast<A>(x) triggers an implicit cast from {x} to type {A}. This is
// useful in situations where static_cast<A>(x) would do too much.
template <class A>
A implicit_cast(A x) {
return x;
}
// Helper to determine how to pass values: Pass scalars and arrays by value,
// others by const reference (even if it was a non-const ref before; this is
// disallowed by the style guide anyway).
......
......@@ -7,6 +7,7 @@
#include <functional>
#include "src/base/macros.h"
#include "src/compiler/code-assembler.h"
#include "src/globals.h"
#include "src/objects.h"
......@@ -409,8 +410,8 @@ class V8_EXPORT_PRIVATE CodeStubAssembler : public compiler::CodeAssembler {
const G& false_body) {
return UncheckedCast<A>(SelectImpl(
condition,
[&]() -> Node* { return base::implicit_cast<TNode<A>>(true_body()); },
[&]() -> Node* { return base::implicit_cast<TNode<A>>(false_body()); },
[&]() -> Node* { return implicit_cast<TNode<A>>(true_body()); },
[&]() -> Node* { return implicit_cast<TNode<A>>(false_body()); },
MachineRepresentationOf<A>::value));
}
......@@ -2550,7 +2551,7 @@ class ToDirectStringAssembler : public CodeStubAssembler {
#define CSA_CHECK(csa, x) \
(csa)->Check( \
[&]() -> compiler::Node* { \
return base::implicit_cast<compiler::SloppyTNode<Word32T>>(x); \
return implicit_cast<compiler::SloppyTNode<Word32T>>(x); \
}, \
#x, __FILE__, __LINE__)
......@@ -2584,7 +2585,7 @@ class ToDirectStringAssembler : public CodeStubAssembler {
#define CSA_ASSERT(csa, ...) \
(csa)->Assert( \
[&]() -> compiler::Node* { \
return base::implicit_cast<compiler::SloppyTNode<Word32T>>( \
return implicit_cast<compiler::SloppyTNode<Word32T>>( \
EXPAND(CSA_ASSERT_GET_FIRST(__VA_ARGS__))); \
}, \
EXPAND(CSA_ASSERT_GET_FIRST_STR(__VA_ARGS__)), __FILE__, __LINE__, \
......
......@@ -11,7 +11,7 @@
// Clients of this interface shouldn't depend on lots of compiler internals.
// Do not include anything from src/compiler here!
#include "src/allocation.h"
#include "src/base/template-utils.h"
#include "src/base/macros.h"
#include "src/builtins/builtins.h"
#include "src/code-factory.h"
#include "src/globals.h"
......@@ -608,7 +608,7 @@ class V8_EXPORT_PRIVATE CodeAssembler {
template <class A>
operator SloppyTNode<A>() {
return base::implicit_cast<TNode<A>>(*this);
return implicit_cast<TNode<A>>(*this);
}
Node* node() const { return node_; }
......@@ -949,7 +949,7 @@ class V8_EXPORT_PRIVATE CodeAssembler {
TNode<Object> CallRuntime(Runtime::FunctionId function,
SloppyTNode<Object> context, TArgs... args) {
return CallRuntimeImpl(function, context,
base::implicit_cast<SloppyTNode<Object>>(args)...);
implicit_cast<SloppyTNode<Object>>(args)...);
}
template <class... TArgs>
......@@ -958,8 +958,8 @@ class V8_EXPORT_PRIVATE CodeAssembler {
template <class... TArgs>
TNode<Object> TailCallRuntime(Runtime::FunctionId function,
SloppyTNode<Object> context, TArgs... args) {
return TailCallRuntimeImpl(
function, context, base::implicit_cast<SloppyTNode<Object>>(args)...);
return TailCallRuntimeImpl(function, context,
implicit_cast<SloppyTNode<Object>>(args)...);
}
//
......@@ -970,14 +970,14 @@ class V8_EXPORT_PRIVATE CodeAssembler {
Node* CallStub(Callable const& callable, Node* context, TArgs... args) {
Node* target = HeapConstant(callable.code());
return CallStub(callable.descriptor(), target, context,
base::implicit_cast<Node*>(args)...);
implicit_cast<Node*>(args)...);
}
template <class... TArgs>
Node* CallStub(const CallInterfaceDescriptor& descriptor, Node* target,
Node* context, TArgs... args) {
return CallStubR(descriptor, 1, target, context,
base::implicit_cast<Node*>(args)...);
implicit_cast<Node*>(args)...);
}
template <class... TArgs>
......@@ -998,7 +998,7 @@ class V8_EXPORT_PRIVATE CodeAssembler {
Node* TailCallStub(const CallInterfaceDescriptor& descriptor, Node* target,
Node* context, TArgs... args) {
return TailCallStubImpl(descriptor, target, context,
base::implicit_cast<Node*>(args)...);
implicit_cast<Node*>(args)...);
}
template <class... TArgs>
Node* TailCallStubImpl(const CallInterfaceDescriptor& descriptor,
......
......@@ -4,7 +4,7 @@
#include <string>
#include "src/base/template-utils.h"
#include "src/base/macros.h"
#include "src/torque/ast-generator.h"
namespace v8 {
......@@ -129,12 +129,12 @@ antlrcpp::Any AstGenerator::visitModuleDeclaration(
result->declarations.push_back(
declaration->accept(this).as<Declaration*>());
}
return base::implicit_cast<Declaration*>(result);
return implicit_cast<Declaration*>(result);
}
antlrcpp::Any AstGenerator::visitMacroDeclaration(
TorqueParser::MacroDeclarationContext* context) {
return base::implicit_cast<Declaration*>(RegisterNode(new MacroDeclaration{
return implicit_cast<Declaration*>(RegisterNode(new MacroDeclaration{
Pos(context), context->IDENTIFIER()->getSymbol()->getText(),
GetOptionalParameterList(context->parameterList()),
GetOptionalType(context->optionalType()),
......@@ -144,7 +144,7 @@ antlrcpp::Any AstGenerator::visitMacroDeclaration(
antlrcpp::Any AstGenerator::visitBuiltinDeclaration(
TorqueParser::BuiltinDeclarationContext* context) {
return base::implicit_cast<Declaration*>(RegisterNode(new BuiltinDeclaration{
return implicit_cast<Declaration*>(RegisterNode(new BuiltinDeclaration{
Pos(context), context->JAVASCRIPT() != nullptr,
context->IDENTIFIER()->getSymbol()->getText(),
std::move(context->parameterList()->accept(this).as<ParameterList>()),
......@@ -165,12 +165,12 @@ antlrcpp::Any AstGenerator::visitExternalMacro(
GetOptionalLabelAndTypeList(context->optionalLabelList())});
if (auto* op = context->STRING_LITERAL())
result->op = StringLiteralUnquote(op->getSymbol()->getText());
return base::implicit_cast<Declaration*>(result);
return implicit_cast<Declaration*>(result);
}
antlrcpp::Any AstGenerator::visitExternalBuiltin(
TorqueParser::ExternalBuiltinContext* context) {
return base::implicit_cast<Declaration*>(
return implicit_cast<Declaration*>(
RegisterNode(new ExternalBuiltinDeclaration{
Pos(context), context->JAVASCRIPT() != nullptr,
context->IDENTIFIER()->getSymbol()->getText(),
......@@ -180,7 +180,7 @@ antlrcpp::Any AstGenerator::visitExternalBuiltin(
antlrcpp::Any AstGenerator::visitExternalRuntime(
TorqueParser::ExternalRuntimeContext* context) {
return base::implicit_cast<Declaration*>(
return implicit_cast<Declaration*>(
RegisterNode(new ExternalRuntimeDeclaration{
Pos(context), context->IDENTIFIER()->getSymbol()->getText(),
std::move(context->typeListMaybeVarArgs()
......@@ -191,7 +191,7 @@ antlrcpp::Any AstGenerator::visitExternalRuntime(
antlrcpp::Any AstGenerator::visitConstDeclaration(
TorqueParser::ConstDeclarationContext* context) {
return base::implicit_cast<Declaration*>(RegisterNode(new ConstDeclaration{
return implicit_cast<Declaration*>(RegisterNode(new ConstDeclaration{
Pos(context), context->IDENTIFIER()->getSymbol()->getText(),
context->type()->IDENTIFIER()->getSymbol()->getText(),
StringLiteralUnquote(
......@@ -211,7 +211,7 @@ antlrcpp::Any AstGenerator::visitTypeDeclaration(
->getSymbol()
->getText());
}
return base::implicit_cast<Declaration*>(result);
return implicit_cast<Declaration*>(result);
}
antlrcpp::Any AstGenerator::visitVariableDeclaration(
......@@ -232,7 +232,7 @@ antlrcpp::Any AstGenerator::visitVariableDeclarationWithInitialization(
result->pos = Pos(context);
if (context->expression())
result->initializer = context->expression()->accept(this).as<Expression*>();
return base::implicit_cast<Statement*>(result);
return implicit_cast<Statement*>(result);
}
antlrcpp::Any AstGenerator::visitHelperCall(
......@@ -251,7 +251,7 @@ antlrcpp::Any AstGenerator::visitHelperCall(
for (auto* arg : context->argumentList()->argument()) {
result->arguments.push_back(arg->accept(this).as<Expression*>());
}
return base::implicit_cast<Expression*>(result);
return implicit_cast<Expression*>(result);
}
antlrcpp::Any AstGenerator::visitHelperCallStatement(
......@@ -276,30 +276,30 @@ antlrcpp::Any AstGenerator::visitStatementScope(
for (auto* child : context->statementList()->statement()) {
result->statements.push_back(child->accept(this).as<Statement*>());
}
return base::implicit_cast<Statement*>(result);
return implicit_cast<Statement*>(result);
}
antlrcpp::Any AstGenerator::visitExpressionStatement(
TorqueParser::ExpressionStatementContext* context) {
return base::implicit_cast<Statement*>(RegisterNode(new ExpressionStatement{
return implicit_cast<Statement*>(RegisterNode(new ExpressionStatement{
Pos(context), context->assignment()->accept(this).as<Expression*>()}));
}
antlrcpp::Any AstGenerator::visitReturnStatement(
TorqueParser::ReturnStatementContext* context) {
return base::implicit_cast<Statement*>(RegisterNode(new ReturnStatement{
return implicit_cast<Statement*>(RegisterNode(new ReturnStatement{
Pos(context), context->expression()->accept(this).as<Expression*>()}));
}
antlrcpp::Any AstGenerator::visitBreakStatement(
TorqueParser::BreakStatementContext* context) {
return base::implicit_cast<Statement*>(
return implicit_cast<Statement*>(
RegisterNode(new BreakStatement{Pos(context)}));
}
antlrcpp::Any AstGenerator::visitContinueStatement(
TorqueParser::ContinueStatementContext* context) {
return base::implicit_cast<Statement*>(
return implicit_cast<Statement*>(
RegisterNode(new ContinueStatement{Pos(context)}));
}
......@@ -314,7 +314,7 @@ antlrcpp::Any AstGenerator::visitGotoStatement(
result->arguments.push_back(a->accept(this).as<Expression*>());
}
}
return base::implicit_cast<Statement*>(result);
return implicit_cast<Statement*>(result);
}
antlrcpp::Any AstGenerator::visitIfStatement(
......@@ -327,12 +327,12 @@ antlrcpp::Any AstGenerator::visitIfStatement(
if (context->statementBlock(1))
result->if_false =
std::move(context->statementBlock(1)->accept(this).as<Statement*>());
return base::implicit_cast<Statement*>(result);
return implicit_cast<Statement*>(result);
}
antlrcpp::Any AstGenerator::visitWhileLoop(
TorqueParser::WhileLoopContext* context) {
return base::implicit_cast<Statement*>(RegisterNode(new WhileStatement{
return implicit_cast<Statement*>(RegisterNode(new WhileStatement{
Pos(context), context->expression()->accept(this).as<Expression*>(),
context->statementBlock()->accept(this).as<Statement*>()}));
}
......@@ -350,7 +350,7 @@ antlrcpp::Any AstGenerator::visitForLoop(
result->var_declaration =
VarDeclarationStatement::cast(init->accept(this).as<Statement*>());
}
return base::implicit_cast<Statement*>(result);
return implicit_cast<Statement*>(result);
}
antlrcpp::Any AstGenerator::visitForOfLoop(
......@@ -372,7 +372,7 @@ antlrcpp::Any AstGenerator::visitForOfLoop(
result->end = end->accept(this).as<Expression*>();
}
}
return base::implicit_cast<Statement*>(result);
return implicit_cast<Statement*>(result);
}
antlrcpp::Any AstGenerator::visitTryCatch(
......@@ -406,24 +406,24 @@ antlrcpp::Any AstGenerator::visitTryCatch(
result->label_blocks.push_back(label_block);
}
}
return base::implicit_cast<Statement*>(result);
return implicit_cast<Statement*>(result);
}
antlrcpp::Any AstGenerator::visitPrimaryExpression(
TorqueParser::PrimaryExpressionContext* context) {
if (auto* e = context->helperCall()) return e->accept(this);
if (auto* e = context->DECIMAL_LITERAL())
return base::implicit_cast<Expression*>(RegisterNode(
return implicit_cast<Expression*>(RegisterNode(
new NumberLiteralExpression{Pos(context), e->getSymbol()->getText()}));
if (auto* e = context->STRING_LITERAL())
return base::implicit_cast<Expression*>(RegisterNode(
return implicit_cast<Expression*>(RegisterNode(
new StringLiteralExpression{Pos(context), e->getSymbol()->getText()}));
if (context->CONVERT_KEYWORD())
return base::implicit_cast<Expression*>(RegisterNode(new ConvertExpression{
return implicit_cast<Expression*>(RegisterNode(new ConvertExpression{
Pos(context), context->type()->IDENTIFIER()->getSymbol()->getText(),
context->expression()->accept(this).as<Expression*>()}));
if (context->CAST_KEYWORD())
return base::implicit_cast<Expression*>(RegisterNode(new CastExpression{
return implicit_cast<Expression*>(RegisterNode(new CastExpression{
Pos(context), context->type()->IDENTIFIER()->getSymbol()->getText(),
context->IDENTIFIER()->getSymbol()->getText(),
context->expression()->accept(this).as<Expression*>()}));
......@@ -442,15 +442,15 @@ antlrcpp::Any AstGenerator::visitAssignment(
std::string op = op_node->getSymbol()->getText();
result->op = op.substr(0, op.length() - 1);
}
return base::implicit_cast<Expression*>(result);
return implicit_cast<Expression*>(result);
}
return base::implicit_cast<Expression*>(location);
return implicit_cast<Expression*>(location);
}
antlrcpp::Any AstGenerator::visitIncrementDecrement(
TorqueParser::IncrementDecrementContext* context) {
bool postfix = context->op;
return base::implicit_cast<Expression*>(
return implicit_cast<Expression*>(
RegisterNode(new IncrementDecrementExpression{
Pos(context),
LocationExpression::cast(
......@@ -465,16 +465,15 @@ antlrcpp::Any AstGenerator::visitLocationExpression(
if (auto* l = context->locationExpression()) {
Expression* location = l->accept(this).as<Expression*>();
if (auto* e = context->expression()) {
return base::implicit_cast<Expression*>(
return implicit_cast<Expression*>(
RegisterNode(new ElementAccessExpression{
Pos(context), location, e->accept(this).as<Expression*>()}));
}
return base::implicit_cast<Expression*>(
RegisterNode(new FieldAccessExpression{
return implicit_cast<Expression*>(RegisterNode(new FieldAccessExpression{
Pos(context), location,
context->IDENTIFIER()->getSymbol()->getText()}));
}
return base::implicit_cast<Expression*>(RegisterNode(new IdentifierExpression{
return implicit_cast<Expression*>(RegisterNode(new IdentifierExpression{
Pos(context), context->IDENTIFIER()->getSymbol()->getText()}));
}
......@@ -483,7 +482,7 @@ antlrcpp::Any AstGenerator::visitUnaryExpression(
if (auto* e = context->assignmentExpression()) return e->accept(this);
std::vector<Expression*> args;
args.push_back(context->unaryExpression()->accept(this).as<Expression*>());
return base::implicit_cast<Expression*>(RegisterNode(new CallExpression{
return implicit_cast<Expression*>(RegisterNode(new CallExpression{
Pos(context), context->op->getText(), true, std::move(args), {}}));
}
......@@ -491,7 +490,7 @@ antlrcpp::Any AstGenerator::visitMultiplicativeExpression(
TorqueParser::MultiplicativeExpressionContext* context) {
auto* right = context->unaryExpression();
if (auto* left = context->multiplicativeExpression()) {
return base::implicit_cast<Expression*>(
return implicit_cast<Expression*>(
RegisterNode(new CallExpression{Pos(context),
context->op->getText(),
true,
......@@ -506,7 +505,7 @@ antlrcpp::Any AstGenerator::visitAdditiveExpression(
TorqueParser::AdditiveExpressionContext* context) {
auto* right = context->multiplicativeExpression();
if (auto* left = context->additiveExpression()) {
return base::implicit_cast<Expression*>(
return implicit_cast<Expression*>(
RegisterNode(new CallExpression{Pos(context),
context->op->getText(),
true,
......@@ -521,7 +520,7 @@ antlrcpp::Any AstGenerator::visitShiftExpression(
TorqueParser::ShiftExpressionContext* context) {
auto* right = context->additiveExpression();
if (auto* left = context->shiftExpression()) {
return base::implicit_cast<Expression*>(
return implicit_cast<Expression*>(
RegisterNode(new CallExpression{Pos(context),
context->op->getText(),
true,
......@@ -536,7 +535,7 @@ antlrcpp::Any AstGenerator::visitRelationalExpression(
TorqueParser::RelationalExpressionContext* context) {
auto* right = context->shiftExpression();
if (auto* left = context->relationalExpression()) {
return base::implicit_cast<Expression*>(
return implicit_cast<Expression*>(
RegisterNode(new CallExpression{Pos(context),
context->op->getText(),
true,
......@@ -551,7 +550,7 @@ antlrcpp::Any AstGenerator::visitEqualityExpression(
TorqueParser::EqualityExpressionContext* context) {
auto* right = context->relationalExpression();
if (auto* left = context->equalityExpression()) {
return base::implicit_cast<Expression*>(
return implicit_cast<Expression*>(
RegisterNode(new CallExpression{Pos(context),
context->op->getText(),
true,
......@@ -566,7 +565,7 @@ antlrcpp::Any AstGenerator::visitBitwiseExpression(
TorqueParser::BitwiseExpressionContext* context) {
auto* right = context->equalityExpression();
if (auto* left = context->bitwiseExpression()) {
return base::implicit_cast<Expression*>(
return implicit_cast<Expression*>(
RegisterNode(new CallExpression{Pos(context),
context->op->getText(),
true,
......@@ -581,8 +580,7 @@ antlrcpp::Any AstGenerator::visitLogicalANDExpression(
TorqueParser::LogicalANDExpressionContext* context) {
auto* right = context->bitwiseExpression();
if (auto* left = context->logicalANDExpression()) {
return base::implicit_cast<Expression*>(
RegisterNode(new LogicalAndExpression{
return implicit_cast<Expression*>(RegisterNode(new LogicalAndExpression{
Pos(context), left->accept(this).as<Expression*>(),
right->accept(this).as<Expression*>()}));
}
......@@ -593,8 +591,7 @@ antlrcpp::Any AstGenerator::visitLogicalORExpression(
TorqueParser::LogicalORExpressionContext* context) {
auto* right = context->logicalANDExpression();
if (auto* left = context->logicalORExpression()) {
return base::implicit_cast<Expression*>(
RegisterNode(new LogicalOrExpression{
return implicit_cast<Expression*>(RegisterNode(new LogicalOrExpression{
Pos(context), left->accept(this).as<Expression*>(),
right->accept(this).as<Expression*>()}));
}
......@@ -604,8 +601,7 @@ antlrcpp::Any AstGenerator::visitLogicalORExpression(
antlrcpp::Any AstGenerator::visitConditionalExpression(
TorqueParser::ConditionalExpressionContext* context) {
if (auto* condition = context->conditionalExpression()) {
return base::implicit_cast<Expression*>(
RegisterNode(new ConditionalExpression{
return implicit_cast<Expression*>(RegisterNode(new ConditionalExpression{
Pos(context), condition->accept(this).as<Expression*>(),
context->logicalORExpression(0)->accept(this).as<Expression*>(),
context->logicalORExpression(1)->accept(this).as<Expression*>()}));
......@@ -620,15 +616,15 @@ antlrcpp::Any AstGenerator::visitDiagnosticStatement(
size_t b = context->expression()->stop->getStopIndex();
antlr4::misc::Interval interval(a, b);
std::string source = source_file_context_->stream->getText(interval);
return base::implicit_cast<Statement*>(RegisterNode(new AssertStatement{
return implicit_cast<Statement*>(RegisterNode(new AssertStatement{
Pos(context), context->expression()->accept(this).as<Expression*>(),
source}));
} else if (context->UNREACHABLE_TOKEN()) {
return base::implicit_cast<Statement*>(
return implicit_cast<Statement*>(
RegisterNode(new DebugStatement{Pos(context), "unreachable", true}));
} else {
DCHECK(context->DEBUG_TOKEN());
return base::implicit_cast<Statement*>(
return implicit_cast<Statement*>(
RegisterNode(new DebugStatement{Pos(context), "debug", false}));
}
}
......
......@@ -8,6 +8,7 @@
#include <set>
#include <string>
#include "src/base/macros.h"
#include "src/torque/declarations.h"
#include "src/torque/file-visitor.h"
#include "src/torque/global-context.h"
......@@ -43,11 +44,11 @@ class DeclarationVisitor : public FileVisitor {
}
void Visit(DefaultModuleDeclaration* decl) {
decl->SetModule(global_context_.GetDefaultModule());
Visit(base::implicit_cast<ModuleDeclaration*>(decl));
Visit(implicit_cast<ModuleDeclaration*>(decl));
}
void Visit(ExplicitModuleDeclaration* decl) {
decl->SetModule(global_context_.GetModule(decl->name));
Visit(base::implicit_cast<ModuleDeclaration*>(decl));
Visit(implicit_cast<ModuleDeclaration*>(decl));
}
void Visit(IdentifierExpression* expr) {}
......
......@@ -7,6 +7,7 @@
#include <string>
#include "src/base/macros.h"
#include "src/torque/ast.h"
#include "src/torque/file-visitor.h"
#include "src/torque/global-context.h"
......@@ -90,10 +91,10 @@ class ImplementationVisitor : public FileVisitor {
void Visit(ModuleDeclaration* decl);
void Visit(DefaultModuleDeclaration* decl) {
Visit(base::implicit_cast<ModuleDeclaration*>(decl));
Visit(implicit_cast<ModuleDeclaration*>(decl));
}
void Visit(ExplicitModuleDeclaration* decl) {
Visit(base::implicit_cast<ModuleDeclaration*>(decl));
Visit(implicit_cast<ModuleDeclaration*>(decl));
}
void Visit(MacroDeclaration* decl);
void Visit(BuiltinDeclaration* decl);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment