Bug 771636 part 1. Rearrange default-value handling so we actually set C++ values directly instead of round-tripping through jsval. r=peterv

This commit is contained in:
Boris Zbarsky 2012-07-31 00:22:22 -04:00
parent ed5dda2b46
commit d988f0953a
4 changed files with 170 additions and 61 deletions

View File

@ -1355,6 +1355,14 @@ builtinNames = {
IDLType.Tags.double: 'double'
}
numericTags = [
IDLType.Tags.int8, IDLType.Tags.uint8,
IDLType.Tags.int16, IDLType.Tags.uint16,
IDLType.Tags.int32, IDLType.Tags.uint32,
IDLType.Tags.int64, IDLType.Tags.uint64,
IDLType.Tags.float, IDLType.Tags.double
]
class CastableObjectUnwrapper():
"""
A class for unwrapping an object named by the "source" argument
@ -1440,7 +1448,8 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None,
isDefinitelyObject=False,
isMember=False,
isOptional=False,
invalidEnumValueFatal=True):
invalidEnumValueFatal=True,
defaultValue=None):
"""
Get a template for converting a JS value to a native object based on the
given type and descriptor. If failureCode is given, then we're actually
@ -1461,6 +1470,12 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None,
If isOptional is true, then we are doing conversion of an optional
argument with no default value.
invalidEnumValueFatal controls whether an invalid enum value conversion
attempt will throw (if true) or simply return without doing anything (if
false).
If defaultValue is not None, it's the IDL default value for this conversion
The return value from this function is a tuple consisting of four things:
1) A string representing the conversion code. This will have template
@ -1470,6 +1485,9 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None,
${valPtr} is a pointer to the JS::Value in question
${holderName} replaced by the holder's name, if any
${declName} replaced by the declaration's name
${haveValue} replaced by an expression that evaluates to a boolean
for whether we have a JS::Value. Only used when
defaultValue is not None.
2) A CGThing representing the native C++ type we're converting to
(declType). This is allowed to be None if the conversion code is
@ -1488,6 +1506,12 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None,
If holderType is not None then ${holderName} must be in scope
before the generated code is entered.
"""
# If we have a defaultValue then we're not actually optional for
# purposes of what we need to be declared as.
assert(defaultValue is None or not isOptional)
# Also, we should not have a defaultValue if we know we're an object
assert(not isDefinitelyObject or defaultValue is None)
# A helper function for dealing with failures due to the JS value being the
# wrong type of value
@ -1497,6 +1521,29 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None,
"return Throw<%s>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);"
% toStringBool(isWorker)), post="\n")
# A helper function for handling default values. Takes a template
# body and the C++ code to set the default value and wraps the
# given template body in handling for the default value.
def handleDefault(template, setDefault):
if defaultValue is None:
return template
return CGWrapper(
CGIndenter(CGGeneric(template)),
pre="if (${haveValue}) {\n",
post=("\n"
"} else {\n"
"%s;\n"
"}" %
CGIndenter(CGGeneric(setDefault)).define())).define()
# A helper function for handling null default values. Much like
# handleDefault, but checks that the default value, if it exists, is null.
def handleDefaultNull(template, codeToSetNull):
if (defaultValue is not None and
not isinstance(defaultValue, IDLNullValue)):
raise TypeError("Can't handle non-null default value here")
return handleDefault(template, codeToSetNull)
# A helper function for wrapping up the template body for
# possibly-nullable objecty stuff
def wrapObjectTemplate(templateBody, isDefinitelyObject, type,
@ -1515,6 +1562,11 @@ def getJSToNativeConversionTemplate(type, descriptorProvider, failureCode=None,
"} else {\n" +
CGIndenter(onFailure(failureCode, isWorker)).define() +
"}")
if type.nullable():
templateBody = handleDefaultNull(templateBody, codeToSetNull)
else:
assert(defaultValue is None)
return templateBody
if type.isArray():
@ -1602,11 +1654,13 @@ for (uint32_t i = 0; i < length; ++i) {
if isMember:
raise TypeError("Can't handle unions as members, we have a "
"holderType")
nullable = type.nullable();
if nullable:
type = type.inner
assert(defaultValue is None or
(isinstance(defaultValue, IDLNullValue) and nullable))
unionArgumentObj = "${holderName}"
if isOptional or nullable:
unionArgumentObj += ".ref()"
@ -1759,10 +1813,10 @@ for (uint32_t i = 0; i < length; ++i) {
nonConstDecl = "const_cast<" + typeName + "& >(${declName})"
typeName = "const " + typeName
def handleNull(templateBody, setToNullVar):
null = CGGeneric("if (${val}.isNullOrUndefined()) {\n"
def handleNull(templateBody, setToNullVar, extraConditionForNull=""):
null = CGGeneric("if (%s${val}.isNullOrUndefined()) {\n"
" %s.SetNull();\n"
"}" % setToNullVar)
"}" % (extraConditionForNull, setToNullVar))
templateBody = CGWrapper(CGIndenter(templateBody), pre="{\n", post="\n}")
return CGList([null, templateBody], " else ")
@ -1792,7 +1846,13 @@ for (uint32_t i = 0; i < length; ++i) {
templateBody = CGList([constructHolder, templateBody], "\n")
if nullable:
templateBody = handleNull(templateBody, mutableDecl)
if defaultValue:
assert(isinstance(defaultValue, IDLNullValue))
valueMissing = "!(${haveValue}) || "
else:
valueMissing = ""
templateBody = handleNull(templateBody, mutableDecl,
extraConditionForNull=valueMissing)
templateBody = CGList([constructDecl, templateBody], "\n")
return templateBody.define(), declType, holderType, False
@ -1980,6 +2040,18 @@ for (uint32_t i = 0; i < length; ++i) {
nullBehavior = "eStringify"
undefinedBehavior = "eStringify"
if defaultValue is not None:
if not isinstance(defaultValue, IDLNullValue):
raise TypeError("Can't handle non-null default values for "
"strings yet")
if not type.nullable():
raise TypeError("Null default value for non-nullable string")
val = "(${haveValue}) ? ${val} : JSVAL_NULL"
valPtr = "(${haveValue}) ? ${valPtr} : NULL"
else:
val = "${val}"
valPtr = "${valPtr}"
if isMember:
# We have to make a copy, because our jsval may well not
# live as long as our string needs to.
@ -1987,12 +2059,12 @@ for (uint32_t i = 0; i < length; ++i) {
return (
"{\n"
" FakeDependentString str;\n"
" if (!ConvertJSValueToString(cx, ${val}, ${valPtr}, %s, %s, str)) {\n"
" if (!ConvertJSValueToString(cx, %s, %s, %s, %s, str)) {\n"
" return false;\n"
" }\n"
" ${declName} = str;\n"
"}\n" %
(nullBehavior, undefinedBehavior),
(val, valPtr, nullBehavior, undefinedBehavior),
declType, None,
isOptional)
@ -2001,16 +2073,19 @@ for (uint32_t i = 0; i < length; ++i) {
else:
declType = "NonNull<nsAString>"
return (
"if (!ConvertJSValueToString(cx, ${val}, ${valPtr}, %s, %s, ${holderName})) {\n"
"if (!ConvertJSValueToString(cx, %s, %s, %s, %s, ${holderName})) {\n"
" return false;\n"
"}\n"
"const_cast<%s&>(${declName}) = &${holderName};" %
(nullBehavior, undefinedBehavior, declType),
(val, valPtr, nullBehavior, undefinedBehavior, declType),
CGGeneric("const " + declType), CGGeneric("FakeDependentString"),
# No need to deal with Optional here; we have handled it already
False)
if type.isEnum():
if defaultValue is not None:
raise TypeError("Can't handle default values for enums")
if type.nullable():
raise TypeError("We don't support nullable enumerated arguments "
"yet")
@ -2044,18 +2119,26 @@ for (uint32_t i = 0; i < length; ++i) {
"rooting issues")
# XXXbz we're going to assume that callback types are always
# nullable and always have [TreatNonCallableAsNull] for now.
haveCallable = "${val}.isObject() && JS_ObjectIsCallable(cx, &${val}.toObject())"
if defaultValue is not None:
assert(isinstance(defaultValue, IDLNullValue))
haveCallable = "${haveValue} && " + haveCallable
return (
"if (${val}.isObject() && JS_ObjectIsCallable(cx, &${val}.toObject())) {\n"
"if (%s) {\n"
" ${declName} = &${val}.toObject();\n"
"} else {\n"
" ${declName} = NULL;\n"
"}", CGGeneric("JSObject*"), None, isOptional)
"}" % haveCallable,
CGGeneric("JSObject*"), None, isOptional)
if type.isAny():
if isMember:
raise TypeError("Can't handle member 'any'; need to sort out "
"rooting issues")
return ("${declName} = ${val};", CGGeneric("JS::Value"), None, isOptional)
templateBody = "${declName} = ${val};"
templateBody = handleDefaultNull(templateBody,
"${declName} = JS::NullValue()")
return (templateBody, CGGeneric("JS::Value"), None, isOptional)
if type.isObject():
if isMember:
@ -2093,9 +2176,17 @@ for (uint32_t i = 0; i < length; ++i) {
declType = CGWrapper(declType, pre="const ")
selfRef = "const_cast<%s&>(%s)" % (typeName, selfRef)
template = ("if (!%s.Init(cx, ${val})) {\n"
# We do manual default value handling here, because we
# actually do want a jsval, and we only handle null anyway
if defaultValue is not None:
assert(isinstance(defaultValue, IDLNullValue))
val = "(${haveValue}) ? ${val} : JSVAL_NULL"
else:
val = "${val}"
template = ("if (!%s.Init(cx, %s)) {\n"
" return false;\n"
"}" % selfRef)
"}" % (selfRef, val))
return (template, declType, None, False)
@ -2105,15 +2196,43 @@ for (uint32_t i = 0; i < length; ++i) {
# XXXbz need to add support for [EnforceRange] and [Clamp]
typeName = builtinNames[type.tag()]
if type.nullable():
return ("if (${val}.isNullOrUndefined()) {\n"
" ${declName}.SetNull();\n"
"} else if (!ValueToPrimitive<" + typeName + ">(cx, ${val}, &${declName}.SetValue())) {\n"
" return false;\n"
"}", CGGeneric("Nullable<" + typeName + ">"), None, isOptional)
dataLoc = "${declName}.SetValue()"
nullCondition = "${val}.isNullOrUndefined()"
if defaultValue is not None and isinstance(defaultValue, IDLNullValue):
nullCondition = "!(${haveValue}) || " + nullCondition
template = (
"if (%s) {\n"
" ${declName}.SetNull();\n"
"} else if (!ValueToPrimitive<%s>(cx, ${val}, &%s)) {\n"
" return false;\n"
"}" % (nullCondition, typeName, dataLoc))
declType = CGGeneric("Nullable<" + typeName + ">")
else:
return ("if (!ValueToPrimitive<" + typeName + ">(cx, ${val}, &${declName})) {\n"
" return false;\n"
"}", CGGeneric(typeName), None, isOptional)
assert(defaultValue is None or
not isinstance(defaultValue, IDLNullValue))
dataLoc = "${declName}"
template = (
"if (!ValueToPrimitive<%s>(cx, ${val}, &%s)) {\n"
" return false;\n"
"}" % (typeName, dataLoc))
declType = CGGeneric(typeName)
if (defaultValue is not None and
# We already handled IDLNullValue, so just deal with the other ones
not isinstance(defaultValue, IDLNullValue)):
tag = defaultValue.type.tag()
if tag in numericTags:
defaultStr = defaultValue.value
else:
assert(tag == IDLType.Tags.bool)
defaultStr = toStringBool(defaultValue.value)
template = CGWrapper(CGIndenter(CGGeneric(template)),
pre="if (${haveValue}) {\n",
post=("\n"
"} else {\n"
" %s = %s;\n"
"}" % (dataLoc, defaultStr))).define()
return (template, declType, None, isOptional)
def instantiateJSToNativeConversionTemplate(templateTuple, replacements,
argcAndIndex=None):
@ -2216,14 +2335,6 @@ def convertConstIDLValueToJSVal(value):
return "DOUBLE_TO_JSVAL(%s)" % (value.value)
raise TypeError("Const value of unhandled type: " + value.type)
def convertIDLDefaultValueToJSVal(value):
if value.type:
tag = value.type.tag()
if (tag == IDLType.Tags.domstring and
(not value.type.nullable() or not isinstance(value, IDLNullValue))):
assert False # Not implemented!
return convertConstIDLValueToJSVal(value)
class CGArgumentConverter(CGThing):
"""
A class that takes an IDL argument object, its index in the
@ -2237,8 +2348,8 @@ class CGArgumentConverter(CGThing):
if argument.variadic:
raise TypeError("We don't support variadic arguments yet " +
str(argument.location))
# XXXbz should optional jsval args get JSVAL_VOID? What about
# others?
assert(not argument.defaultValue or argument.optional)
replacer = {
"index" : index,
"argc" : argc,
@ -2248,20 +2359,14 @@ class CGArgumentConverter(CGThing):
"declName" : "arg%d" % index,
"holderName" : ("arg%d" % index) + "_holder"
}
if argument.optional and argument.defaultValue:
replacer["defaultValue"] = convertIDLDefaultValueToJSVal(argument.defaultValue)
self.replacementVariables["val"] = string.Template(
"(${index} < ${argc} ? ${argv}[${index}] : ${defaultValue})"
).substitute(replacer)
self.replacementVariables["valPtr"] = string.Template(
"(${index} < ${argc} ? &${argv}[${index}] : NULL)"
).substitute(replacer)
else:
self.replacementVariables["val"] = string.Template(
"${argv}[${index}]"
).substitute(replacer)
self.replacementVariables["valPtr"] = (
"&" + self.replacementVariables["val"])
self.replacementVariables["val"] = string.Template(
"${argv}[${index}]"
).substitute(replacer)
self.replacementVariables["valPtr"] = (
"&" + self.replacementVariables["val"])
if argument.defaultValue:
self.replacementVariables["haveValue"] = string.Template(
"${index} < ${argc}").substitute(replacer)
self.descriptorProvider = descriptorProvider
if self.argument.optional and not self.argument.defaultValue:
self.argcAndIndex = replacer
@ -2274,7 +2379,8 @@ class CGArgumentConverter(CGThing):
getJSToNativeConversionTemplate(self.argument.type,
self.descriptorProvider,
isOptional=(self.argcAndIndex is not None),
invalidEnumValueFatal=self.invalidEnumValueFatal),
invalidEnumValueFatal=self.invalidEnumValueFatal,
defaultValue=self.argument.defaultValue),
self.replacementVariables,
self.argcAndIndex).define()
@ -3051,6 +3157,7 @@ class FakeArgument():
self.type = type
self.optional = False
self.variadic = False
self.defaultValue = None
class CGSetterCall(CGGetterSetterCall):
"""
@ -4067,7 +4174,8 @@ class CGDictionary(CGThing):
getJSToNativeConversionTemplate(member.type,
descriptorProvider,
isMember=True,
isOptional=(not member.defaultValue)))
isOptional=(not member.defaultValue),
defaultValue=member.defaultValue))
for member in dictionary.members ]
except NoSuchDescriptorError, err:
if not self.workers:
@ -4204,6 +4312,8 @@ class CGDictionary(CGThing):
assert holderType is None
if dealWithOptional:
replacements["declName"] = "(" + replacements["declName"] + ".Value())"
if member.defaultValue:
replacements["haveValue"] = "found"
conversionReplacements = {
"propId" : self.makeIdName(member.identifier.name),
@ -4221,12 +4331,8 @@ class CGDictionary(CGThing):
" if (!JS_GetPropertyById(cx, &val.toObject(), ${propId}, &temp)) {\n"
" return false;\n"
" }\n"
"} else {\n"
" temp = ${defaultVal};\n"
"}\n"
"${convert}")
conversionReplacements["defaultVal"] = (
convertIDLDefaultValueToJSVal(member.defaultValue))
else:
conversion += (
"if (found) {\n"

View File

@ -1753,7 +1753,8 @@ class IDLNullValue(IDLObject):
def coerceToType(self, type, location):
if (not isinstance(type, IDLNullableType) and
not (type.isUnion() and type.hasNullableType) and
not type.isDictionary()):
not type.isDictionary() and
not type.isAny()):
raise WebIDLError("Cannot coerce null value to type %s." % type,
[location])

View File

@ -361,6 +361,7 @@ public:
// Any types
void PassAny(JSContext*, JS::Value, ErrorResult&);
void PassOptionalAny(JSContext*, const Optional<JS::Value>&, ErrorResult&);
void PassAnyDefaultNull(JSContext*, JS::Value, ErrorResult&);
JS::Value ReceiveAny(JSContext*, ErrorResult&);
// object types

View File

@ -51,49 +51,49 @@ interface TestInterface {
void passShort(short arg);
short receiveShort();
void passOptionalShort(optional short arg);
void passOptionalShortWithDefault(optional short arg = 0);
void passOptionalShortWithDefault(optional short arg = 5);
readonly attribute long readonlyLong;
attribute long writableLong;
void passLong(long arg);
long receiveLong();
void passOptionalLong(optional long arg);
void passOptionalLongWithDefault(optional long arg = 0);
void passOptionalLongWithDefault(optional long arg = 7);
readonly attribute long long readonlyLongLong;
attribute long long writableLongLong;
void passLongLong(long long arg);
long long receiveLongLong();
void passOptionalLongLong(optional long long arg);
void passOptionalLongLongWithDefault(optional long long arg = 0);
void passOptionalLongLongWithDefault(optional long long arg = -12);
readonly attribute octet readonlyOctet;
attribute octet writableOctet;
void passOctet(octet arg);
octet receiveOctet();
void passOptionalOctet(optional octet arg);
void passOptionalOctetWithDefault(optional octet arg = 0);
void passOptionalOctetWithDefault(optional octet arg = 19);
readonly attribute unsigned short readonlyUnsignedShort;
attribute unsigned short writableUnsignedShort;
void passUnsignedShort(unsigned short arg);
unsigned short receiveUnsignedShort();
void passOptionalUnsignedShort(optional unsigned short arg);
void passOptionalUnsignedShortWithDefault(optional unsigned short arg = 0);
void passOptionalUnsignedShortWithDefault(optional unsigned short arg = 2);
readonly attribute unsigned long readonlyUnsignedLong;
attribute unsigned long writableUnsignedLong;
void passUnsignedLong(unsigned long arg);
unsigned long receiveUnsignedLong();
void passOptionalUnsignedLong(optional unsigned long arg);
void passOptionalUnsignedLongWithDefault(optional unsigned long arg = 0);
void passOptionalUnsignedLongWithDefault(optional unsigned long arg = 6);
readonly attribute unsigned long long readonlyUnsignedLongLong;
attribute unsigned long long writableUnsignedLongLong;
void passUnsignedLongLong(unsigned long long arg);
unsigned long long receiveUnsignedLongLong();
void passOptionalUnsignedLongLong(optional unsigned long long arg);
void passOptionalUnsignedLongLongWithDefault(optional unsigned long long arg = 0);
void passOptionalUnsignedLongLongWithDefault(optional unsigned long long arg = 17);
// Castable interface types
// XXXbz add tests for infallible versions of all the castable interface stuff
@ -259,6 +259,7 @@ interface TestInterface {
// Any types
void passAny(any arg);
void passOptionalAny(optional any arg);
void passAnyDefaultNull(optional any arg = null);
any receiveAny();
// object types