Commit e2accb42 authored by Michael Starzinger's avatar Michael Starzinger Committed by Commit Bot

[asm.js] Fix numeric literal bounds checking.

This fixes the bounds checking of "unsigned" numeric literals (those
that do not contains dots) by the parser. In particular this fixes a
bogus truncation to 32-bit in the scanner. It also makes the scanner
more robust by limiting the range of those numeric literals, hence
completely avoiding rounding loss or truncation errors.

R=clemensh@chromium.org
TEST=unittests/AsmJsScannerTest.UnsignedNumbers
BUG=v8:6298

Change-Id: Id31ab3c652e99fa8d3d6663315768e1bfaf3b773
Reviewed-on: https://chromium-review.googlesource.com/486881Reviewed-by: 's avatarClemens Hammacher <clemensh@chromium.org>
Commit-Queue: Michael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#44890}
parent bfae9db9
...@@ -437,7 +437,7 @@ void AsmJsParser::ValidateModuleVar(bool mutable_variable) { ...@@ -437,7 +437,7 @@ void AsmJsParser::ValidateModuleVar(bool mutable_variable) {
} }
EXPECT_TOKEN('='); EXPECT_TOKEN('=');
double dvalue = 0.0; double dvalue = 0.0;
uint64_t uvalue = 0; uint32_t uvalue = 0;
if (CheckForDouble(&dvalue)) { if (CheckForDouble(&dvalue)) {
DeclareGlobal(info, mutable_variable, AsmType::Double(), kWasmF64, DeclareGlobal(info, mutable_variable, AsmType::Double(), kWasmF64,
WasmInitExpr(dvalue)); WasmInitExpr(dvalue));
...@@ -504,7 +504,7 @@ void AsmJsParser::ValidateModuleVarFromGlobal(VarInfo* info, ...@@ -504,7 +504,7 @@ void AsmJsParser::ValidateModuleVarFromGlobal(VarInfo* info,
negate = true; negate = true;
} }
double dvalue = 0.0; double dvalue = 0.0;
uint64_t uvalue = 0; uint32_t uvalue = 0;
if (CheckForDouble(&dvalue)) { if (CheckForDouble(&dvalue)) {
if (negate) { if (negate) {
dvalue = -dvalue; dvalue = -dvalue;
...@@ -871,7 +871,7 @@ void AsmJsParser::ValidateFunctionLocals( ...@@ -871,7 +871,7 @@ void AsmJsParser::ValidateFunctionLocals(
// Store types. // Store types.
EXPECT_TOKEN('='); EXPECT_TOKEN('=');
double dvalue = 0.0; double dvalue = 0.0;
uint64_t uvalue = 0; uint32_t uvalue = 0;
if (Check('-')) { if (Check('-')) {
if (CheckForDouble(&dvalue)) { if (CheckForDouble(&dvalue)) {
info->kind = VarKind::kLocal; info->kind = VarKind::kLocal;
...@@ -1298,7 +1298,7 @@ void AsmJsParser::ValidateCase() { ...@@ -1298,7 +1298,7 @@ void AsmJsParser::ValidateCase() {
if (Check('-')) { if (Check('-')) {
negate = true; negate = true;
} }
uint64_t uvalue; uint32_t uvalue;
if (!CheckForUnsigned(&uvalue)) { if (!CheckForUnsigned(&uvalue)) {
FAIL("Expected numeric literal"); FAIL("Expected numeric literal");
} }
...@@ -1359,7 +1359,7 @@ AsmType* AsmJsParser::Expression(AsmType* expected) { ...@@ -1359,7 +1359,7 @@ AsmType* AsmJsParser::Expression(AsmType* expected) {
AsmType* AsmJsParser::NumericLiteral() { AsmType* AsmJsParser::NumericLiteral() {
call_coercion_ = nullptr; call_coercion_ = nullptr;
double dvalue = 0.0; double dvalue = 0.0;
uint64_t uvalue = 0; uint32_t uvalue = 0;
if (CheckForDouble(&dvalue)) { if (CheckForDouble(&dvalue)) {
current_function_builder_->EmitF64Const(dvalue); current_function_builder_->EmitF64Const(dvalue);
return AsmType::Double(); return AsmType::Double();
...@@ -1519,7 +1519,7 @@ AsmType* AsmJsParser::AssignmentExpression() { ...@@ -1519,7 +1519,7 @@ AsmType* AsmJsParser::AssignmentExpression() {
AsmType* AsmJsParser::UnaryExpression() { AsmType* AsmJsParser::UnaryExpression() {
AsmType* ret; AsmType* ret;
if (Check('-')) { if (Check('-')) {
uint64_t uvalue; uint32_t uvalue;
if (CheckForUnsigned(&uvalue)) { if (CheckForUnsigned(&uvalue)) {
// TODO(bradnelson): was supposed to be 0x7fffffff, check errata. // TODO(bradnelson): was supposed to be 0x7fffffff, check errata.
if (uvalue <= 0x80000000) { if (uvalue <= 0x80000000) {
...@@ -1601,7 +1601,7 @@ AsmType* AsmJsParser::UnaryExpression() { ...@@ -1601,7 +1601,7 @@ AsmType* AsmJsParser::UnaryExpression() {
// 6.8.8 MultaplicativeExpression // 6.8.8 MultaplicativeExpression
AsmType* AsmJsParser::MultiplicativeExpression() { AsmType* AsmJsParser::MultiplicativeExpression() {
uint64_t uvalue; uint32_t uvalue;
if (CheckForUnsignedBelow(0x100000, &uvalue)) { if (CheckForUnsignedBelow(0x100000, &uvalue)) {
if (Check('*')) { if (Check('*')) {
AsmType* a; AsmType* a;
...@@ -1634,7 +1634,7 @@ AsmType* AsmJsParser::MultiplicativeExpression() { ...@@ -1634,7 +1634,7 @@ AsmType* AsmJsParser::MultiplicativeExpression() {
RECURSEn(a = UnaryExpression()); RECURSEn(a = UnaryExpression());
for (;;) { for (;;) {
if (Check('*')) { if (Check('*')) {
uint64_t uvalue; uint32_t uvalue;
if (Check('-')) { if (Check('-')) {
if (CheckForUnsigned(&uvalue)) { if (CheckForUnsigned(&uvalue)) {
if (uvalue >= 0x100000) { if (uvalue >= 0x100000) {
...@@ -2014,10 +2014,12 @@ AsmType* AsmJsParser::ValidateCall() { ...@@ -2014,10 +2014,12 @@ AsmType* AsmJsParser::ValidateCall() {
if (Check('[')) { if (Check('[')) {
RECURSEn(EqualityExpression()); RECURSEn(EqualityExpression());
EXPECT_TOKENn('&'); EXPECT_TOKENn('&');
uint64_t mask = 0; uint32_t mask = 0;
if (!CheckForUnsigned(&mask)) { if (!CheckForUnsigned(&mask)) {
FAILn("Expected mask literal"); FAILn("Expected mask literal");
} }
// TODO(mstarzinger): Clarify and explain where this limit is coming from,
// as it is not mandated by the spec directly.
if (mask > 0x7fffffff) { if (mask > 0x7fffffff) {
FAILn("Expected power of 2 mask"); FAILn("Expected power of 2 mask");
} }
...@@ -2326,10 +2328,14 @@ void AsmJsParser::ValidateHeapAccess() { ...@@ -2326,10 +2328,14 @@ void AsmJsParser::ValidateHeapAccess() {
VarInfo* info = GetVarInfo(Consume()); VarInfo* info = GetVarInfo(Consume());
int32_t size = info->type->ElementSizeInBytes(); int32_t size = info->type->ElementSizeInBytes();
EXPECT_TOKEN('['); EXPECT_TOKEN('[');
uint64_t offset; uint32_t offset;
if (CheckForUnsigned(&offset)) { if (CheckForUnsigned(&offset)) {
// TODO(bradnelson): Check more things. // TODO(bradnelson): Check more things.
if (offset > 0x7fffffff || offset * size > 0x7fffffff) { // TODO(mstarzinger): Clarify and explain where this limit is coming from,
// as it is not mandated by the spec directly.
if (offset > 0x7fffffff ||
static_cast<uint64_t>(offset) * static_cast<uint64_t>(size) >
0x7fffffff) {
FAIL("Heap access out of range"); FAIL("Heap access out of range");
} }
if (Check(']')) { if (Check(']')) {
...@@ -2349,7 +2355,7 @@ void AsmJsParser::ValidateHeapAccess() { ...@@ -2349,7 +2355,7 @@ void AsmJsParser::ValidateHeapAccess() {
} else { } else {
RECURSE(index_type = AdditiveExpression()); RECURSE(index_type = AdditiveExpression());
EXPECT_TOKEN(TOK(SAR)); EXPECT_TOKEN(TOK(SAR));
uint64_t shift; uint32_t shift;
if (!CheckForUnsigned(&shift)) { if (!CheckForUnsigned(&shift)) {
FAIL("Expected shift of word size"); FAIL("Expected shift of word size");
} }
...@@ -2430,7 +2436,7 @@ void AsmJsParser::GatherCases(std::vector<int32_t>* cases) { ...@@ -2430,7 +2436,7 @@ void AsmJsParser::GatherCases(std::vector<int32_t>* cases) {
} else if (depth == 1 && Peek(TOK(case))) { } else if (depth == 1 && Peek(TOK(case))) {
scanner_.Next(); scanner_.Next();
int32_t value; int32_t value;
uint64_t uvalue; uint32_t uvalue;
if (Check('-')) { if (Check('-')) {
if (!CheckForUnsigned(&uvalue)) { if (!CheckForUnsigned(&uvalue)) {
break; break;
......
...@@ -192,7 +192,7 @@ class AsmJsParser { ...@@ -192,7 +192,7 @@ class AsmJsParser {
} }
} }
inline bool CheckForUnsigned(uint64_t* value) { inline bool CheckForUnsigned(uint32_t* value) {
if (scanner_.IsUnsigned()) { if (scanner_.IsUnsigned()) {
*value = scanner_.AsUnsigned(); *value = scanner_.AsUnsigned();
scanner_.Next(); scanner_.Next();
...@@ -202,7 +202,7 @@ class AsmJsParser { ...@@ -202,7 +202,7 @@ class AsmJsParser {
} }
} }
inline bool CheckForUnsignedBelow(uint64_t limit, uint64_t* value) { inline bool CheckForUnsignedBelow(uint32_t limit, uint32_t* value) {
if (scanner_.IsUnsigned() && scanner_.AsUnsigned() < limit) { if (scanner_.IsUnsigned() && scanner_.AsUnsigned() < limit) {
*value = scanner_.AsUnsigned(); *value = scanner_.AsUnsigned();
scanner_.Next(); scanner_.Next();
......
...@@ -72,7 +72,7 @@ void AsmJsScanner::Next() { ...@@ -72,7 +72,7 @@ void AsmJsScanner::Next() {
if (Token() == kDouble) { if (Token() == kDouble) {
PrintF("%lf ", AsDouble()); PrintF("%lf ", AsDouble());
} else if (Token() == kUnsigned) { } else if (Token() == kUnsigned) {
PrintF("%" PRIu64 " ", AsUnsigned()); PrintF("%" PRIu32 " ", AsUnsigned());
} else { } else {
std::string name = Name(Token()); std::string name = Name(Token());
PrintF("%s ", name.c_str()); PrintF("%s ", name.c_str());
...@@ -308,9 +308,8 @@ void AsmJsScanner::ConsumeNumber(uc32 ch) { ...@@ -308,9 +308,8 @@ void AsmJsScanner::ConsumeNumber(uc32 ch) {
UnicodeCache cache; UnicodeCache cache;
double_value_ = StringToDouble( double_value_ = StringToDouble(
&cache, &cache,
Vector<uint8_t>( Vector<const uint8_t>(reinterpret_cast<const uint8_t*>(number.data()),
const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(number.data())), static_cast<int>(number.size())),
static_cast<int>(number.size())),
ALLOW_HEX | ALLOW_OCTAL | ALLOW_BINARY | ALLOW_IMPLICIT_OCTAL); ALLOW_HEX | ALLOW_OCTAL | ALLOW_BINARY | ALLOW_IMPLICIT_OCTAL);
if (std::isnan(double_value_)) { if (std::isnan(double_value_)) {
// Check if string to number conversion didn't consume all the characters. // Check if string to number conversion didn't consume all the characters.
...@@ -332,6 +331,11 @@ void AsmJsScanner::ConsumeNumber(uc32 ch) { ...@@ -332,6 +331,11 @@ void AsmJsScanner::ConsumeNumber(uc32 ch) {
if (has_dot) { if (has_dot) {
token_ = kDouble; token_ = kDouble;
} else { } else {
// Exceeding safe integer range is an error.
if (double_value_ > static_cast<double>(kMaxUInt32)) {
token_ = kParseError;
return;
}
unsigned_value_ = static_cast<uint32_t>(double_value_); unsigned_value_ = static_cast<uint32_t>(double_value_);
token_ = kUnsigned; token_ = kUnsigned;
} }
......
...@@ -92,12 +92,19 @@ class V8_EXPORT_PRIVATE AsmJsScanner { ...@@ -92,12 +92,19 @@ class V8_EXPORT_PRIVATE AsmJsScanner {
return token - kGlobalsStart; return token - kGlobalsStart;
} }
// Methods to check if the current token is an asm.js "number" (contains a // Methods to check if the current token is a numeric literal considered an
// dot) or an "unsigned" (a number without a dot). // asm.js "double" (contains a dot) or an "unsigned" (without a dot). Note
// that numbers without a dot outside the [0 .. 2^32) range are errors.
bool IsUnsigned() const { return Token() == kUnsigned; } bool IsUnsigned() const { return Token() == kUnsigned; }
uint64_t AsUnsigned() const { return unsigned_value_; } uint32_t AsUnsigned() const {
DCHECK(IsUnsigned());
return unsigned_value_;
}
bool IsDouble() const { return Token() == kDouble; } bool IsDouble() const { return Token() == kDouble; }
double AsDouble() const { return double_value_; } double AsDouble() const {
DCHECK(IsDouble());
return double_value_;
}
// clang-format off // clang-format off
enum { enum {
...@@ -146,7 +153,7 @@ class V8_EXPORT_PRIVATE AsmJsScanner { ...@@ -146,7 +153,7 @@ class V8_EXPORT_PRIVATE AsmJsScanner {
std::unordered_map<std::string, token_t> property_names_; std::unordered_map<std::string, token_t> property_names_;
int global_count_; int global_count_;
double double_value_; double double_value_;
uint64_t unsigned_value_; uint32_t unsigned_value_;
bool preceded_by_newline_; bool preceded_by_newline_;
// Consume multiple characters. // Consume multiple characters.
......
// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --allow-natives-syntax
function Module(stdlib, imports, buffer) {
"use asm";
function f() {
return (281474976710655 * 1048575) | 0;
}
return { f:f };
}
var m = Module(this);
assertEquals(-1048576, m.f());
assertFalse(%IsAsmWasmCode(Module));
...@@ -209,6 +209,26 @@ TEST_F(AsmJsScannerTest, Numbers) { ...@@ -209,6 +209,26 @@ TEST_F(AsmJsScannerTest, Numbers) {
CheckForEnd(); CheckForEnd();
} }
TEST_F(AsmJsScannerTest, UnsignedNumbers) {
SetupSource("0x7fffffff 0x80000000 0xffffffff 0x100000000");
CHECK(scanner.IsUnsigned());
CHECK_EQ(0x7fffffff, scanner.AsUnsigned());
scanner.Next();
CHECK(scanner.IsUnsigned());
CHECK_EQ(0x80000000, scanner.AsUnsigned());
scanner.Next();
CHECK(scanner.IsUnsigned());
CHECK_EQ(0xffffffff, scanner.AsUnsigned());
scanner.Next();
// Numeric "unsigned" literals with a payload of more than 32-bit are rejected
// by asm.js in all contexts, we hence consider `0x100000000` to be an error.
CheckForParseError();
}
TEST_F(AsmJsScannerTest, BadNumber) { TEST_F(AsmJsScannerTest, BadNumber) {
SetupSource(".123fe"); SetupSource(".123fe");
Skip('.'); Skip('.');
......
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