Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(279)

Unified Diff: extensions/renderer/argument_spec.cc

Issue 2837023003: [Extensions Bindings] Add argument parsing errors (Closed)
Patch Set: nits Created 3 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « extensions/renderer/argument_spec.h ('k') | extensions/renderer/argument_spec_unittest.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: extensions/renderer/argument_spec.cc
diff --git a/extensions/renderer/argument_spec.cc b/extensions/renderer/argument_spec.cc
index fd3e1a973a923855127aaa0f1ae9574b8ea9833f..08768595b80ddb9dbb27efc8bf4eaa1b42138af0 100644
--- a/extensions/renderer/argument_spec.cc
+++ b/extensions/renderer/argument_spec.cc
@@ -5,9 +5,10 @@
#include "extensions/renderer/argument_spec.h"
#include "base/memory/ptr_util.h"
-#include "base/strings/stringprintf.h"
+#include "base/strings/string_piece.h"
#include "base/values.h"
#include "content/public/child/v8_value_converter.h"
+#include "extensions/renderer/api_invocation_errors.h"
#include "extensions/renderer/api_type_reference_map.h"
#include "gin/converter.h"
#include "gin/dictionary.h"
@@ -16,21 +17,56 @@ namespace extensions {
namespace {
+// Returns a type string for the given |value|.
+const char* GetV8ValueTypeString(v8::Local<v8::Value> value) {
+ DCHECK(!value.IsEmpty());
+
+ if (value->IsNull())
+ return api_errors::kTypeNull;
+ if (value->IsUndefined())
+ return api_errors::kTypeUndefined;
+ if (value->IsInt32())
+ return api_errors::kTypeInteger;
+ if (value->IsNumber())
+ return api_errors::kTypeDouble;
+ if (value->IsBoolean())
+ return api_errors::kTypeBoolean;
+ if (value->IsString())
+ return api_errors::kTypeString;
+
+ // Note: check IsArray(), IsFunction(), and IsArrayBuffer[View]() before
+ // IsObject() since arrays, functions, and array buffers are objects.
+ if (value->IsArray())
+ return api_errors::kTypeList;
+ if (value->IsFunction())
+ return api_errors::kTypeFunction;
+ if (value->IsArrayBuffer() || value->IsArrayBufferView())
+ return api_errors::kTypeBinary;
+ if (value->IsObject())
+ return api_errors::kTypeObject;
+
+ // TODO(devlin): The list above isn't exhaustive (it's missing at least
+ // Symbol and Uint32). We may want to include those, since saying
+ // "expected int, found other" isn't super helpful. On the other hand, authors
+ // should be able to see what they passed.
+ return "other";
+}
+
+// Returns true if |value| is within the bounds specified by |minimum| and
+// |maximum|, populating |error| otherwise.
template <class T>
-bool ParseFundamentalValueHelper(v8::Local<v8::Value> arg,
- v8::Local<v8::Context> context,
- const base::Optional<int>& minimum,
- const base::Optional<int>& maximum,
- std::unique_ptr<base::Value>* out_value) {
- T val;
- if (!gin::Converter<T>::FromV8(context->GetIsolate(), arg, &val))
- return false;
- if (minimum && val < *minimum)
+bool CheckFundamentalBounds(T value,
+ const base::Optional<int>& minimum,
+ const base::Optional<int>& maximum,
+ std::string* error) {
+ if (minimum && value < *minimum) {
+ *error = api_errors::NumberTooSmall(*minimum);
return false;
- if (maximum && val > *maximum)
+ }
+ if (maximum && value > *maximum) {
+ *error = api_errors::NumberTooLarge(*maximum);
return false;
- if (out_value)
- *out_value = base::MakeUnique<base::Value>(val);
+ }
return true;
}
@@ -168,8 +204,10 @@ bool ArgumentSpec::ParseArgument(v8::Local<v8::Context> context,
std::unique_ptr<base::Value>* out_value,
std::string* error) const {
if (type_ == ArgumentType::FUNCTION) {
- if (!value->IsFunction())
+ if (!value->IsFunction()) {
+ *error = GetInvalidTypeError(value);
return false;
+ }
if (out_value) {
// Certain APIs (contextMenus) have functions as parameters other than the
@@ -194,7 +232,7 @@ bool ArgumentSpec::ParseArgument(v8::Local<v8::Context> context,
if (choice->ParseArgument(context, value, refs, out_value, error))
return true;
}
- *error = "Did not match any of the choices";
+ *error = api_errors::InvalidChoice();
return false;
}
@@ -207,7 +245,7 @@ bool ArgumentSpec::ParseArgument(v8::Local<v8::Context> context,
// parameter and then an optional callback, we wouldn't necessarily be able
// to match the arguments if we allowed functions as objects.
if (!value->IsObject() || value->IsFunction() || value->IsArray()) {
- *error = "Wrong type";
+ *error = GetInvalidTypeError(value);
return false;
}
v8::Local<v8::Object> object = value.As<v8::Object>();
@@ -215,7 +253,7 @@ bool ArgumentSpec::ParseArgument(v8::Local<v8::Context> context,
}
if (type_ == ArgumentType::LIST) {
if (!value->IsArray()) {
- *error = "Wrong type";
+ *error = GetInvalidTypeError(value);
return false;
}
v8::Local<v8::Array> array = value.As<v8::Array>();
@@ -223,7 +261,7 @@ bool ArgumentSpec::ParseArgument(v8::Local<v8::Context> context,
}
if (type_ == ArgumentType::BINARY) {
if (!value->IsArrayBuffer() && !value->IsArrayBufferView()) {
- *error = "Wrong type";
+ *error = GetInvalidTypeError(value);
return false;
}
return ParseArgumentToAny(context, value, out_value, error);
@@ -245,26 +283,47 @@ bool ArgumentSpec::ParseArgumentToFundamental(
std::unique_ptr<base::Value>* out_value,
std::string* error) const {
DCHECK(IsFundamentalType());
+
switch (type_) {
- case ArgumentType::INTEGER:
- return ParseFundamentalValueHelper<int32_t>(value, context, minimum_,
- maximum_, out_value);
- case ArgumentType::DOUBLE:
- return ParseFundamentalValueHelper<double>(value, context, minimum_,
- maximum_, out_value);
+ case ArgumentType::INTEGER: {
+ if (!value->IsInt32()) {
+ *error = GetInvalidTypeError(value);
+ return false;
+ }
+ int int_val = value.As<v8::Int32>()->Value();
+ if (!CheckFundamentalBounds(int_val, minimum_, maximum_, error))
+ return false;
+ if (out_value)
+ *out_value = base::MakeUnique<base::Value>(int_val);
+ return true;
+ }
+ case ArgumentType::DOUBLE: {
+ if (!value->IsNumber()) {
+ *error = GetInvalidTypeError(value);
+ return false;
+ }
+ double double_val = value.As<v8::Number>()->Value();
+ if (!CheckFundamentalBounds(double_val, minimum_, maximum_, error))
+ return false;
+ if (out_value)
+ *out_value = base::MakeUnique<base::Value>(double_val);
+ return true;
+ }
case ArgumentType::STRING: {
- if (!value->IsString())
+ if (!value->IsString()) {
+ *error = GetInvalidTypeError(value);
return false;
+ }
v8::Local<v8::String> v8_string = value.As<v8::String>();
size_t length = static_cast<size_t>(v8_string->Length());
if (min_length_ && length < *min_length_) {
- *error = "Less than min length";
+ *error = api_errors::TooFewStringChars(*min_length_, length);
return false;
}
if (max_length_ && length > *max_length_) {
- *error = "Greater than max length";
+ *error = api_errors::TooManyStringChars(*max_length_, length);
return false;
}
@@ -277,8 +336,10 @@ bool ArgumentSpec::ParseArgumentToFundamental(
// We already checked that this is a string, so this should never fail.
CHECK(gin::Converter<std::string>::FromV8(context->GetIsolate(), value,
&s));
- if (!enum_values_.empty() && enum_values_.count(s) == 0)
+ if (!enum_values_.empty() && enum_values_.count(s) == 0) {
+ *error = api_errors::InvalidEnumValue(enum_values_);
return false;
+ }
if (out_value) {
// TODO(devlin): If base::Value ever takes a std::string&&, we
// could use std::move to construct.
@@ -287,8 +348,10 @@ bool ArgumentSpec::ParseArgumentToFundamental(
return true;
}
case ArgumentType::BOOLEAN: {
- if (!value->IsBoolean())
+ if (!value->IsBoolean()) {
+ *error = GetInvalidTypeError(value);
return false;
+ }
if (out_value) {
*out_value =
base::MakeUnique<base::Value>(value.As<v8::Boolean>()->Value());
@@ -314,17 +377,22 @@ bool ArgumentSpec::ParseArgumentToObject(
result = base::MakeUnique<base::DictionaryValue>();
v8::Local<v8::Array> own_property_names;
- if (!object->GetOwnPropertyNames(context).ToLocal(&own_property_names))
+ if (!object->GetOwnPropertyNames(context).ToLocal(&own_property_names)) {
+ *error = api_errors::ScriptThrewError();
return false;
+ }
// Track all properties we see from |properties_| to check if any are missing.
// Use ArgumentSpec* instead of std::string for comparison + copy efficiency.
std::set<const ArgumentSpec*> seen_properties;
uint32_t length = own_property_names->Length();
+ std::string property_error;
for (uint32_t i = 0; i < length; ++i) {
v8::Local<v8::Value> key;
- if (!own_property_names->Get(context, i).ToLocal(&key))
+ if (!own_property_names->Get(context, i).ToLocal(&key)) {
+ *error = api_errors::ScriptThrewError();
return false;
+ }
// In JS, all keys are strings or numbers (or symbols, but those are
// excluded by GetOwnPropertyNames()). If you try to set anything else
// (e.g. an object), it is converted to a string.
@@ -339,7 +407,7 @@ bool ArgumentSpec::ParseArgumentToObject(
} else if (additional_properties_) {
property_spec = additional_properties_.get();
} else {
- *error = base::StringPrintf("Unknown property: %s", *utf8_key);
+ *error = api_errors::UnexpectedProperty(*utf8_key);
return false;
}
@@ -359,7 +427,7 @@ bool ArgumentSpec::ParseArgumentToObject(
// TODO(devlin): This matches current behavior, but it is correct?
if (prop_value->IsUndefined() || prop_value->IsNull()) {
if (!property_spec->optional_) {
- *error = base::StringPrintf("Missing key: %s", *utf8_key);
+ *error = api_errors::MissingRequiredProperty(*utf8_key);
return false;
}
continue;
@@ -367,7 +435,9 @@ bool ArgumentSpec::ParseArgumentToObject(
std::unique_ptr<base::Value> property;
if (!property_spec->ParseArgument(context, prop_value, refs,
- result ? &property : nullptr, error)) {
+ result ? &property : nullptr,
+ &property_error)) {
+ *error = api_errors::PropertyError(*utf8_key, property_error);
return false;
}
if (result)
@@ -377,7 +447,7 @@ bool ArgumentSpec::ParseArgumentToObject(
for (const auto& pair : properties_) {
const ArgumentSpec* spec = pair.second.get();
if (!spec->optional_ && seen_properties.count(spec) == 0) {
- *error = "Missing key: " + pair.first;
+ *error = api_errors::MissingRequiredProperty(pair.first.c_str());
return false;
}
}
@@ -410,7 +480,7 @@ bool ArgumentSpec::ParseArgumentToObject(
} while (next_check->IsObject());
if (!found) {
- *error = "Object is not of correct instance";
+ *error = api_errors::NotAnInstance(instance_of_->c_str());
return false;
}
}
@@ -429,12 +499,12 @@ bool ArgumentSpec::ParseArgumentToArray(v8::Local<v8::Context> context,
uint32_t length = value->Length();
if (min_length_ && length < *min_length_) {
- *error = "Less than min length";
+ *error = api_errors::TooFewArrayItems(*min_length_, length);
return false;
}
if (max_length_ && length > *max_length_) {
- *error = "Greater than max length";
+ *error = api_errors::TooManyArrayItems(*max_length_, length);
return false;
}
@@ -443,6 +513,7 @@ bool ArgumentSpec::ParseArgumentToArray(v8::Local<v8::Context> context,
if (out_value)
result = base::MakeUnique<base::ListValue>();
+ std::string item_error;
for (uint32_t i = 0; i < length; ++i) {
v8::MaybeLocal<v8::Value> maybe_subvalue = value->Get(context, i);
v8::Local<v8::Value> subvalue;
@@ -456,8 +527,9 @@ bool ArgumentSpec::ParseArgumentToArray(v8::Local<v8::Context> context,
if (!maybe_subvalue.ToLocal(&subvalue))
return false;
std::unique_ptr<base::Value> item;
- if (!list_element_type_->ParseArgument(context, subvalue, refs,
- result ? &item : nullptr, error)) {
+ if (!list_element_type_->ParseArgument(
+ context, subvalue, refs, result ? &item : nullptr, &item_error)) {
+ *error = api_errors::IndexError(i, item_error);
return false;
}
if (result)
@@ -479,7 +551,7 @@ bool ArgumentSpec::ParseArgumentToAny(v8::Local<v8::Context> context,
std::unique_ptr<base::Value> converted(
converter->FromV8Value(value, context));
if (!converted) {
- *error = "Could not convert to 'any'.";
+ *error = api_errors::UnserializableValue();
return false;
}
if (type_ == ArgumentType::BINARY)
@@ -489,4 +561,44 @@ bool ArgumentSpec::ParseArgumentToAny(v8::Local<v8::Context> context,
return true;
}
+std::string ArgumentSpec::GetInvalidTypeError(
+ v8::Local<v8::Value> value) const {
+ const char* expected_type = nullptr;
+ switch (type_) {
+ case ArgumentType::INTEGER:
+ expected_type = api_errors::kTypeInteger;
+ break;
+ case ArgumentType::DOUBLE:
+ expected_type = api_errors::kTypeDouble;
+ break;
+ case ArgumentType::BOOLEAN:
+ expected_type = api_errors::kTypeBoolean;
+ break;
+ case ArgumentType::STRING:
+ expected_type = api_errors::kTypeString;
+ break;
+ case ArgumentType::OBJECT:
+ expected_type =
+ instance_of_ ? instance_of_->c_str() : api_errors::kTypeObject;
+ break;
+ case ArgumentType::LIST:
+ expected_type = api_errors::kTypeList;
+ break;
+ case ArgumentType::BINARY:
+ expected_type = api_errors::kTypeBinary;
+ break;
+ case ArgumentType::FUNCTION:
+ expected_type = api_errors::kTypeFunction;
+ break;
+ case ArgumentType::REF:
+ expected_type = ref_->c_str();
+ break;
+ case ArgumentType::CHOICES:
+ case ArgumentType::ANY:
+ NOTREACHED();
+ }
+
+ return api_errors::InvalidType(expected_type, GetV8ValueTypeString(value));
+}
+
} // namespace extensions
« no previous file with comments | « extensions/renderer/argument_spec.h ('k') | extensions/renderer/argument_spec_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698