aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom/bindings/codegen
diff options
context:
space:
mode:
authorbors-servo <lbergstrom+bors@mozilla.com>2017-09-25 16:07:47 -0500
committerGitHub <noreply@github.com>2017-09-25 16:07:47 -0500
commit532dee36c10b7dd5d33e560b55cf65c7243ef1d3 (patch)
treee6474f4893df3fbfe3ec9bca7c7e709bb47ea669 /components/script/dom/bindings/codegen
parent86b926b4cf78ae3436b946e42caad8362b4841d1 (diff)
parent44822c364c2f1970705834fb0f95df1411089f9d (diff)
downloadservo-532dee36c10b7dd5d33e560b55cf65c7243ef1d3.tar.gz
servo-532dee36c10b7dd5d33e560b55cf65c7243ef1d3.zip
Auto merge of #17056 - jdm:heapdict, r=emilio
Make dictionaries and unions containing GC values safer Problems: * the Heap::new constructor is memory-unsafe with any value other than Undefined/Null * this means that moving dictionaries containing Heap values (ie. any/object) is memory-unsafe * unions containing GC pointers are similarly unsafe Solutions: - dictionaries containing GC pointers are now wrapped in RootedTraceableBox (even inside other dictionaries) - instead of using Heap::new, dictionaries containing GC pointers are now initialized to a default value (by deriving Default) and mutated one field at a time - dictionaries containing GC pointers are marked #[must_root] - FromJSVal for dictionaries containing GC pointers now returns RootedTraceableBox<Dictionary> - unions wrap their variants that require rooting in RootedTraceableBox Rather than attempting to derive Default for all dictionaries, we only do so for the dictionaries that require it. Because some dictionaries that require it inherit from dictionaries that do not, we need to write manual implementations for those parent dictionaries. This is a better option than having to figure out a default value for types like `Root<T>`, which would be required for deriving Default for all dictionaries. I would still like to come up with an automated test for this, but I figured I would get eyes on this first. --- - [X] `./mach build -d` does not report any errors - [X] `./mach test-tidy` does not report any errors - [X] These changes fix #16952 - [ ] There are tests for these changes <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/17056) <!-- Reviewable:end -->
Diffstat (limited to 'components/script/dom/bindings/codegen')
-rw-r--r--components/script/dom/bindings/codegen/CodegenRust.py148
1 files changed, 108 insertions, 40 deletions
diff --git a/components/script/dom/bindings/codegen/CodegenRust.py b/components/script/dom/bindings/codegen/CodegenRust.py
index 9a98848108b..68ab0a33c18 100644
--- a/components/script/dom/bindings/codegen/CodegenRust.py
+++ b/components/script/dom/bindings/codegen/CodegenRust.py
@@ -723,9 +723,6 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
if type.nullable():
declType = CGWrapper(declType, pre="Option<", post=" >")
- if isMember != "Dictionary" and type_needs_tracing(type):
- declType = CGTemplatedType("RootedTraceableBox", declType)
-
templateBody = ("match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n"
" Ok(ConversionResult::Success(value)) => value,\n"
" Ok(ConversionResult::Failure(error)) => {\n"
@@ -1048,12 +1045,12 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
if defaultValue is None:
default = None
elif isinstance(defaultValue, IDLNullValue):
- default = "Heap::new(NullValue())"
+ default = "NullValue()"
elif isinstance(defaultValue, IDLUndefinedValue):
- default = "Heap::new(UndefinedValue())"
+ default = "UndefinedValue()"
else:
raise TypeError("Can't handle non-null, non-undefined default value here")
- return handleOptional("Heap::new(${val}.get())", declType, default)
+ return handleOptional("${val}.get()", declType, default)
declType = CGGeneric("HandleValue")
@@ -1080,8 +1077,6 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
if isMember in ("Dictionary", "Union"):
declType = CGGeneric("Heap<*mut JSObject>")
- templateBody = "Heap::new(%s)" % templateBody
- default = "Heap::new(%s)" % default
else:
# TODO: Need to root somehow
# https://github.com/servo/servo/issues/6382
@@ -1099,9 +1094,8 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
declType = CGGeneric(typeName)
empty = "%s::empty(cx)" % typeName
- if isMember != "Dictionary" and type_needs_tracing(type):
+ if type_needs_tracing(type):
declType = CGTemplatedType("RootedTraceableBox", declType)
- empty = "RootedTraceableBox::new(%s)" % empty
template = ("match FromJSValConvertible::from_jsval(cx, ${val}, ()) {\n"
" Ok(ConversionResult::Success(dictionary)) => dictionary,\n"
@@ -1427,6 +1421,8 @@ def getRetvalDeclarationForType(returnType, descriptorProvider):
nullable = returnType.nullable()
dictName = returnType.inner.name if nullable else returnType.name
result = CGGeneric(dictName)
+ if type_needs_tracing(returnType):
+ result = CGWrapper(result, pre="RootedTraceableBox<", post=">")
if nullable:
result = CGWrapper(result, pre="Option<", post=">")
return result
@@ -2262,6 +2258,7 @@ def UnionTypes(descriptors, dictionaries, callbacks, typedefs, config):
'dom::bindings::str::ByteString',
'dom::bindings::str::DOMString',
'dom::bindings::str::USVString',
+ 'dom::bindings::trace::RootedTraceableBox',
'dom::types::*',
'js::error::throw_type_error',
'js::jsapi::HandleValue',
@@ -4014,27 +4011,36 @@ pub enum %s {
pairs = ",\n ".join(['("%s", super::%s::%s)' % (val, ident, getEnumValueName(val)) for val in enum.values()])
- inner = """\
+ inner = string.Template("""\
use dom::bindings::conversions::ToJSValConvertible;
use js::jsapi::{JSContext, MutableHandleValue};
use js::jsval::JSVal;
-pub const pairs: &'static [(&'static str, super::%s)] = &[
- %s,
+pub const pairs: &'static [(&'static str, super::${ident})] = &[
+ ${pairs},
];
-impl super::%s {
+impl super::${ident} {
pub fn as_str(&self) -> &'static str {
pairs[*self as usize].0
}
}
-impl ToJSValConvertible for super::%s {
+impl Default for super::${ident} {
+ fn default() -> super::${ident} {
+ pairs[0].1
+ }
+}
+
+impl ToJSValConvertible for super::${ident} {
unsafe fn to_jsval(&self, cx: *mut JSContext, rval: MutableHandleValue) {
pairs[*self as usize].0.to_jsval(cx, rval);
}
}
- """ % (ident, pairs, ident, ident)
+ """).substitute({
+ 'ident': ident,
+ 'pairs': pairs
+ })
self.cgRoot = CGList([
CGGeneric(decl),
CGNamespace.build([ident + "Values"],
@@ -4132,15 +4138,23 @@ class CGUnionStruct(CGThing):
self.type = type
self.descriptorProvider = descriptorProvider
+ def membersNeedTracing(self):
+ for t in self.type.flatMemberTypes:
+ if type_needs_tracing(t):
+ return True
+ return False
+
def define(self):
- templateVars = map(lambda t: getUnionTypeTemplateVars(t, self.descriptorProvider),
+ templateVars = map(lambda t: (getUnionTypeTemplateVars(t, self.descriptorProvider),
+ type_needs_tracing(t)),
self.type.flatMemberTypes)
enumValues = [
- " %s(%s)," % (v["name"], v["typeName"]) for v in templateVars
+ " %s(%s)," % (v["name"], "RootedTraceableBox<%s>" % v["typeName"] if trace else v["typeName"])
+ for (v, trace) in templateVars
]
enumConversions = [
" %s::%s(ref inner) => inner.to_jsval(cx, rval),"
- % (self.type, v["name"]) for v in templateVars
+ % (self.type, v["name"]) for (v, _) in templateVars
]
return ("""\
#[derive(JSTraceable)]
@@ -4167,6 +4181,12 @@ class CGUnionConversionStruct(CGThing):
self.type = type
self.descriptorProvider = descriptorProvider
+ def membersNeedTracing(self):
+ for t in self.type.flatMemberTypes:
+ if type_needs_tracing(t):
+ return True
+ return False
+
def from_jsval(self):
memberTypes = self.type.flatMemberTypes
names = []
@@ -4310,13 +4330,20 @@ class CGUnionConversionStruct(CGThing):
def try_method(self, t):
templateVars = getUnionTypeTemplateVars(t, self.descriptorProvider)
- returnType = "Result<Option<%s>, ()>" % templateVars["typeName"]
+ actualType = templateVars["typeName"]
+ if type_needs_tracing(t):
+ actualType = "RootedTraceableBox<%s>" % actualType
+ returnType = "Result<Option<%s>, ()>" % actualType
jsConversion = templateVars["jsConversion"]
+ # Any code to convert to Object is unused, since we're already converting
+ # from an Object value.
+ if t.name == 'Object':
+ return CGGeneric('')
+
return CGWrapper(
CGIndenter(jsConversion, 4),
- # TryConvertToObject is unused, but not generating it while generating others is tricky.
- pre="#[allow(dead_code)] unsafe fn TryConvertTo%s(cx: *mut JSContext, value: HandleValue) -> %s {\n"
+ pre="unsafe fn TryConvertTo%s(cx: *mut JSContext, value: HandleValue) -> %s {\n"
% (t.name, returnType),
post="\n}")
@@ -5682,6 +5709,7 @@ def generate_imports(config, cgthings, descriptors, callbacks=None, dictionaries
'dom::bindings::iterable::Iterable',
'dom::bindings::iterable::IteratorType',
'dom::bindings::js::JS',
+ 'dom::bindings::js::OptionalHeapSetter',
'dom::bindings::js::Root',
'dom::bindings::js::RootedReference',
'dom::bindings::namespace::NamespaceObjectClass',
@@ -6018,18 +6046,27 @@ class CGDictionary(CGThing):
(self.makeMemberName(m[0].identifier.name), self.getMemberType(m))
for m in self.memberInfo]
+ derive = ["JSTraceable"]
+ mustRoot = ""
+ if self.membersNeedTracing():
+ mustRoot = "#[must_root]\n"
+ derive += ["Default"]
+
return (string.Template(
- "#[derive(JSTraceable)]\n"
+ "#[derive(${derive})]\n"
+ "${mustRoot}" +
"pub struct ${selfName} {\n" +
"${inheritance}" +
"\n".join(memberDecls) + "\n" +
"}").substitute({"selfName": self.makeClassName(d),
- "inheritance": inheritance}))
+ "inheritance": inheritance,
+ "mustRoot": mustRoot,
+ "derive": ', '.join(derive)}))
def impl(self):
d = self.dictionary
if d.parent:
- initParent = ("parent: {\n"
+ initParent = ("{\n"
" match try!(%s::%s::new(cx, val)) {\n"
" ConversionResult::Success(v) => v,\n"
" ConversionResult::Failure(error) => {\n"
@@ -6037,16 +6074,20 @@ class CGDictionary(CGThing):
" return Err(());\n"
" }\n"
" }\n"
- "},\n" % (self.makeModuleName(d.parent),
- self.makeClassName(d.parent)))
+ "}" % (self.makeModuleName(d.parent),
+ self.makeClassName(d.parent)))
else:
initParent = ""
- def memberInit(memberInfo):
+ def memberInit(memberInfo, isInitial):
member, _ = memberInfo
name = self.makeMemberName(member.identifier.name)
conversion = self.getMemberConversion(memberInfo, member.type)
- return CGGeneric("%s: %s,\n" % (name, conversion.define()))
+ if isInitial:
+ return CGGeneric("%s: %s,\n" % (name, conversion.define()))
+ if member.type.isAny() or member.type.isObject():
+ return CGGeneric("dictionary.%s.set(%s);\n" % (name, conversion.define()))
+ return CGGeneric("dictionary.%s = %s;\n" % (name, conversion.define()))
def varInsert(varName, dictionaryName):
insertion = ("rooted!(in(cx) let mut %s_js = UndefinedValue());\n"
@@ -6066,19 +6107,32 @@ class CGDictionary(CGThing):
(name, name, varInsert(name, member.identifier.name).define()))
return CGGeneric("%s\n" % insertion.define())
- memberInits = CGList([memberInit(m) for m in self.memberInfo])
memberInserts = CGList([memberInsert(m) for m in self.memberInfo])
+ selfName = self.makeClassName(d)
+ if self.membersNeedTracing():
+ actualType = "RootedTraceableBox<%s>" % selfName
+ preInitial = "let mut dictionary = RootedTraceableBox::new(%s::default());\n" % selfName
+ initParent = initParent = ("dictionary.parent = %s;\n" % initParent) if initParent else ""
+ memberInits = CGList([memberInit(m, False) for m in self.memberInfo])
+ postInitial = ""
+ else:
+ actualType = selfName
+ preInitial = "let dictionary = %s {\n" % selfName
+ postInitial = "};\n"
+ initParent = ("parent: %s,\n" % initParent) if initParent else ""
+ memberInits = CGList([memberInit(m, True) for m in self.memberInfo])
+
return string.Template(
"impl ${selfName} {\n"
- " pub unsafe fn empty(cx: *mut JSContext) -> ${selfName} {\n"
+ " pub unsafe fn empty(cx: *mut JSContext) -> ${actualType} {\n"
" match ${selfName}::new(cx, HandleValue::null()) {\n"
" Ok(ConversionResult::Success(v)) => v,\n"
" _ => unreachable!(),\n"
" }\n"
" }\n"
" pub unsafe fn new(cx: *mut JSContext, val: HandleValue) \n"
- " -> Result<ConversionResult<${selfName}>, ()> {\n"
+ " -> Result<ConversionResult<${actualType}>, ()> {\n"
" let object = if val.get().is_null_or_undefined() {\n"
" ptr::null_mut()\n"
" } else if val.get().is_object() {\n"
@@ -6088,17 +6142,18 @@ class CGDictionary(CGThing):
" return Err(());\n"
" };\n"
" rooted!(in(cx) let object = object);\n"
- " Ok(ConversionResult::Success(${selfName} {\n"
+ "${preInitial}"
"${initParent}"
"${initMembers}"
- " }))\n"
+ "${postInitial}"
+ " Ok(ConversionResult::Success(dictionary))\n"
" }\n"
"}\n"
"\n"
- "impl FromJSValConvertible for ${selfName} {\n"
+ "impl FromJSValConvertible for ${actualType} {\n"
" type Config = ();\n"
" unsafe fn from_jsval(cx: *mut JSContext, value: HandleValue, _option: ())\n"
- " -> Result<ConversionResult<${selfName}>, ()> {\n"
+ " -> Result<ConversionResult<${actualType}>, ()> {\n"
" ${selfName}::new(cx, value)\n"
" }\n"
"}\n"
@@ -6110,12 +6165,21 @@ class CGDictionary(CGThing):
" rval.set(ObjectOrNullValue(obj.get()))\n"
" }\n"
"}\n").substitute({
- "selfName": self.makeClassName(d),
+ "selfName": selfName,
+ "actualType": actualType,
"initParent": CGIndenter(CGGeneric(initParent), indentLevel=12).define(),
"initMembers": CGIndenter(memberInits, indentLevel=12).define(),
"insertMembers": CGIndenter(memberInserts, indentLevel=8).define(),
+ "preInitial": CGIndenter(CGGeneric(preInitial), indentLevel=12).define(),
+ "postInitial": CGIndenter(CGGeneric(postInitial), indentLevel=12).define(),
})
+ def membersNeedTracing(self):
+ for member, _ in self.memberInfo:
+ if type_needs_tracing(member.type):
+ return True
+ return False
+
@staticmethod
def makeDictionaryName(dictionary):
return dictionary.identifier.name
@@ -6638,7 +6702,7 @@ class CallbackMember(CGNativeMember):
replacements["argCount"] = self.argCountStr
replacements["argvDecl"] = string.Template(
"rooted_vec!(let mut argv);\n"
- "argv.extend((0..${argCount}).map(|_| Heap::new(UndefinedValue())));\n"
+ "argv.extend((0..${argCount}).map(|_| Heap::default()));\n"
).substitute(replacements)
else:
# Avoid weird 0-sized arrays
@@ -6713,7 +6777,11 @@ class CallbackMember(CGNativeMember):
conversion = wrapForType(
"argv_root.handle_mut()", result=argval,
- successCode="argv[%s] = Heap::new(argv_root.get());" % jsvalIndex,
+ successCode=("{\n" +
+ "let arg = &mut argv[%s];\n" +
+ "*arg = Heap::default();\n" +
+ "arg.set(argv_root.get());\n" +
+ "}") % jsvalIndex,
pre="rooted!(in(cx) let mut argv_root = UndefinedValue());")
if arg.variadic:
conversion = string.Template(
@@ -6729,7 +6797,7 @@ class CallbackMember(CGNativeMember):
" // This is our current trailing argument; reduce argc\n"
" argc -= 1;\n"
"} else {\n"
- " argv[%d] = Heap::new(UndefinedValue());\n"
+ " argv[%d] = Heap::default();\n"
"}" % (i + 1, i))
return conversion