Commit 2aa04298 authored by Ken Rockot's avatar Ken Rockot Committed by Commit Bot

Do not scamble message IDs with explicit ordinals

This prevents the bindings generator from re-assigning a message's ID to
a scrambled value whenever the message definition in mojom has an
explicit ordinal declared (i.e. @N notation).

As an added measure of stability, this also adds the requirement that
interfaces marked [Stable] MUST assign explicit ordinals to all methods.

Fixed: 1089230
Change-Id: Iaa6eff350f7ecee31feb54b19349c9caa4b2a2c8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2225327
Commit-Queue: Ken Rockot <rockot@google.com>
Reviewed-by: default avatarHidehiko Abe <hidehiko@chromium.org>
Cr-Commit-Position: refs/heads/master@{#774041}
parent 74722e04
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
# found in the LICENSE file. # found in the LICENSE file.
"""Generates C++ source files from a mojom.Module.""" """Generates C++ source files from a mojom.Module."""
import os import os
import sys
from mojom_cpp_generator import _NameFormatter as CppNameFormatter from mojom_cpp_generator import _NameFormatter as CppNameFormatter
from mojom_cpp_generator import Generator as CppGenerator from mojom_cpp_generator import Generator as CppGenerator
from mojom_cpp_generator import IsNativeOnlyKind, NamespaceToArray from mojom_cpp_generator import IsNativeOnlyKind, NamespaceToArray
...@@ -40,6 +41,12 @@ _kind_to_cpp_proto_type = { ...@@ -40,6 +41,12 @@ _kind_to_cpp_proto_type = {
} }
def _IsStrOrUnicode(x):
if sys.version_info[0] < 3:
return isinstance(x, (unicode, str))
return isinstance(x, str)
class _NameFormatter(CppNameFormatter): class _NameFormatter(CppNameFormatter):
"""A formatter for the names of kinds or values.""" """A formatter for the names of kinds or values."""
...@@ -479,7 +486,7 @@ class Generator(CppGenerator): ...@@ -479,7 +486,7 @@ class Generator(CppGenerator):
if field.name == 'MAX': if field.name == 'MAX':
continue continue
if field.value: if field.value:
if isinstance(field.value, str): if _IsStrOrUnicode(field.value):
if field.value in values: if field.value in values:
return True return True
values.add(field.value) values.add(field.value)
......
...@@ -109,6 +109,8 @@ def ScrambleMethodOrdinals(interfaces, salt): ...@@ -109,6 +109,8 @@ def ScrambleMethodOrdinals(interfaces, salt):
i = 0 i = 0
already_generated.clear() already_generated.clear()
for method in interface.methods: for method in interface.methods:
if method.explicit_ordinal is not None:
continue
while True: while True:
i = i + 1 i = i + 1
if i == 1000000: if i == 1000000:
......
...@@ -9,14 +9,15 @@ from mojom_bindings_generator import ScrambleMethodOrdinals ...@@ -9,14 +9,15 @@ from mojom_bindings_generator import ScrambleMethodOrdinals
class FakeIface(object): class FakeIface(object):
def __init__( self ): def __init__(self):
self.name = None self.mojom_name = None
self.methods = None self.methods = None
class FakeMethod(object): class FakeMethod(object):
def __init__( self ): def __init__(self, explicit_ordinal=None):
self.ordinal = None self.explicit_ordinal = explicit_ordinal
self.ordinal = explicit_ordinal
self.ordinal_comment = None self.ordinal_comment = None
...@@ -25,18 +26,23 @@ class MojoBindingsGeneratorTest(unittest.TestCase): ...@@ -25,18 +26,23 @@ class MojoBindingsGeneratorTest(unittest.TestCase):
def testMakeImportStackMessage(self): def testMakeImportStackMessage(self):
"""Tests MakeImportStackMessage().""" """Tests MakeImportStackMessage()."""
self.assertEquals(MakeImportStackMessage(["x"]), "") self.assertEqual(MakeImportStackMessage(["x"]), "")
self.assertEquals(MakeImportStackMessage(["x", "y"]), self.assertEqual(MakeImportStackMessage(["x", "y"]),
"\n y was imported by x") "\n y was imported by x")
self.assertEquals(MakeImportStackMessage(["x", "y", "z"]), self.assertEqual(MakeImportStackMessage(["x", "y", "z"]),
"\n z was imported by y\n y was imported by x") "\n z was imported by y\n y was imported by x")
def testScrambleMethodOrdinals(self): def testScrambleMethodOrdinals(self):
"""Tests ScrambleMethodOrdinals().""" """Tests ScrambleMethodOrdinals()."""
interface = FakeIface() interface = FakeIface()
interface.name = 'RendererConfiguration' interface.mojom_name = 'RendererConfiguration'
interface.methods = [FakeMethod(), FakeMethod(), FakeMethod()] interface.methods = [
ScrambleMethodOrdinals([interface], "foo") FakeMethod(),
FakeMethod(),
FakeMethod(),
FakeMethod(explicit_ordinal=42)
]
ScrambleMethodOrdinals([interface], "foo".encode('utf-8'))
# These next three values are hard-coded. If the generation algorithm # These next three values are hard-coded. If the generation algorithm
# changes from being based on sha256(seed + interface.name + str(i)) then # changes from being based on sha256(seed + interface.name + str(i)) then
# these numbers will obviously need to change too. # these numbers will obviously need to change too.
...@@ -44,9 +50,12 @@ class MojoBindingsGeneratorTest(unittest.TestCase): ...@@ -44,9 +50,12 @@ class MojoBindingsGeneratorTest(unittest.TestCase):
# Note that hashlib.sha256('fooRendererConfiguration1').digest()[:4] is # Note that hashlib.sha256('fooRendererConfiguration1').digest()[:4] is
# '\xa5\xbc\xf9\xca' and that hex(1257880741) = '0x4af9bca5'. The # '\xa5\xbc\xf9\xca' and that hex(1257880741) = '0x4af9bca5'. The
# difference in 0x4a vs 0xca is because we only take 31 bits. # difference in 0x4a vs 0xca is because we only take 31 bits.
self.assertEquals(interface.methods[0].ordinal, 1257880741) self.assertEqual(interface.methods[0].ordinal, 1257880741)
self.assertEquals(interface.methods[1].ordinal, 631133653) self.assertEqual(interface.methods[1].ordinal, 631133653)
self.assertEquals(interface.methods[2].ordinal, 549336076) self.assertEqual(interface.methods[2].ordinal, 549336076)
# Explicit method ordinals should not be scrambled.
self.assertEqual(interface.methods[3].ordinal, 42)
if __name__ == "__main__": if __name__ == "__main__":
......
...@@ -12,6 +12,7 @@ This can be used e.g. by a presubmit check to prevent developers from making ...@@ -12,6 +12,7 @@ This can be used e.g. by a presubmit check to prevent developers from making
breaking changes to stable mojoms.""" breaking changes to stable mojoms."""
import argparse import argparse
import codecs
import errno import errno
import json import json
import os import os
...@@ -60,10 +61,10 @@ def _ValidateDelta(root, delta): ...@@ -60,10 +61,10 @@ def _ValidateDelta(root, delta):
modules = override_modules modules = override_modules
else: else:
modules = unmodified_modules modules = unmodified_modules
with open(os.path.join(root, mojom)) as f: with codecs.open(os.path.join(root, mojom), encoding='utf-8') as f:
contents = ''.join(f.readlines()) contents = ''.join(f.readlines())
ast = parser.Parse(contents.encode('utf-8'), mojom) ast = parser.Parse(contents, mojom)
for imp in ast.import_list: for imp in ast.import_list:
parseMojom(imp.import_filename, file_overrides, override_modules) parseMojom(imp.import_filename, file_overrides, override_modules)
......
...@@ -175,6 +175,11 @@ def AddComputedData(module): ...@@ -175,6 +175,11 @@ def AddComputedData(module):
method.param_struct = _GetStructFromMethod(method) method.param_struct = _GetStructFromMethod(method)
if interface.stable: if interface.stable:
method.param_struct.attributes[mojom.ATTRIBUTE_STABLE] = True method.param_struct.attributes[mojom.ATTRIBUTE_STABLE] = True
if method.explicit_ordinal is None:
raise Exception(
'Stable interfaces must declare explicit method ordinals. The '
'method %s on stable interface %s does not declare an explicit '
'ordinal.' % (method.mojom_name, interface.qualified_name))
interface.version = max(interface.version, interface.version = max(interface.version,
method.param_struct.versions[-1].version) method.param_struct.versions[-1].version)
......
...@@ -910,6 +910,7 @@ class Method(object): ...@@ -910,6 +910,7 @@ class Method(object):
self.interface = interface self.interface = interface
self.mojom_name = mojom_name self.mojom_name = mojom_name
self.name = None self.name = None
self.explicit_ordinal = ordinal
self.ordinal = ordinal self.ordinal = ordinal
self.parameters = [] self.parameters = []
self.param_struct = None self.param_struct = None
......
...@@ -12,12 +12,19 @@ already been parsed and converted to ASTs before. ...@@ -12,12 +12,19 @@ already been parsed and converted to ASTs before.
import itertools import itertools
import os import os
import re import re
import sys
from mojom.generate import generator from mojom.generate import generator
from mojom.generate import module as mojom from mojom.generate import module as mojom
from mojom.parse import ast from mojom.parse import ast
def _IsStrOrUnicode(x):
if sys.version_info[0] < 3:
return isinstance(x, (unicode, str))
return isinstance(x, str)
def _DuplicateName(values): def _DuplicateName(values):
"""Returns the 'mojom_name' of the first entry in |values| whose 'mojom_name' """Returns the 'mojom_name' of the first entry in |values| whose 'mojom_name'
has already been encountered. If there are no duplicates, returns None.""" has already been encountered. If there are no duplicates, returns None."""
...@@ -534,7 +541,7 @@ def _ResolveNumericEnumValues(enum): ...@@ -534,7 +541,7 @@ def _ResolveNumericEnumValues(enum):
prev_value += 1 prev_value += 1
# Integral value (e.g: BEGIN = -0x1). # Integral value (e.g: BEGIN = -0x1).
elif isinstance(field.value, str): elif _IsStrOrUnicode(field.value):
prev_value = int(field.value, 0) prev_value = int(field.value, 0)
# Reference to a previous enum value (e.g: INIT = BEGIN). # Reference to a previous enum value (e.g: INIT = BEGIN).
......
...@@ -9,6 +9,15 @@ ...@@ -9,6 +9,15 @@
# failures, especially for more complex types. # failures, especially for more complex types.
import sys
def _IsStrOrUnicode(x):
if sys.version_info[0] < 3:
return isinstance(x, (unicode, str))
return isinstance(x, str)
class NodeBase(object): class NodeBase(object):
"""Base class for nodes in the AST.""" """Base class for nodes in the AST."""
...@@ -87,7 +96,7 @@ class Definition(NodeBase): ...@@ -87,7 +96,7 @@ class Definition(NodeBase):
include parameter definitions.) This class is meant to be subclassed.""" include parameter definitions.) This class is meant to be subclassed."""
def __init__(self, mojom_name, **kwargs): def __init__(self, mojom_name, **kwargs):
assert isinstance(mojom_name, str) assert _IsStrOrUnicode(mojom_name)
NodeBase.__init__(self, **kwargs) NodeBase.__init__(self, **kwargs)
self.mojom_name = mojom_name self.mojom_name = mojom_name
...@@ -99,7 +108,7 @@ class Attribute(NodeBase): ...@@ -99,7 +108,7 @@ class Attribute(NodeBase):
"""Represents an attribute.""" """Represents an attribute."""
def __init__(self, key, value, **kwargs): def __init__(self, key, value, **kwargs):
assert isinstance(key, str) assert _IsStrOrUnicode(key)
super(Attribute, self).__init__(**kwargs) super(Attribute, self).__init__(**kwargs)
self.key = key self.key = key
self.value = value self.value = value
...@@ -122,10 +131,10 @@ class Const(Definition): ...@@ -122,10 +131,10 @@ class Const(Definition):
def __init__(self, mojom_name, attribute_list, typename, value, **kwargs): def __init__(self, mojom_name, attribute_list, typename, value, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList) assert attribute_list is None or isinstance(attribute_list, AttributeList)
# The typename is currently passed through as a string. # The typename is currently passed through as a string.
assert isinstance(typename, str) assert _IsStrOrUnicode(typename)
# The value is either a literal (currently passed through as a string) or a # The value is either a literal (currently passed through as a string) or a
# "wrapped identifier". # "wrapped identifier".
assert isinstance(value, str) or isinstance(value, tuple) assert _IsStrOrUnicode or isinstance(value, tuple)
super(Const, self).__init__(mojom_name, **kwargs) super(Const, self).__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list self.attribute_list = attribute_list
self.typename = typename self.typename = typename
...@@ -161,7 +170,7 @@ class EnumValue(Definition): ...@@ -161,7 +170,7 @@ class EnumValue(Definition):
# The optional value is either an int (which is current a string) or a # The optional value is either an int (which is current a string) or a
# "wrapped identifier". # "wrapped identifier".
assert attribute_list is None or isinstance(attribute_list, AttributeList) assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert value is None or isinstance(value, (str, tuple)) assert value is None or _IsStrOrUnicode(value) or isinstance(value, tuple)
super(EnumValue, self).__init__(mojom_name, **kwargs) super(EnumValue, self).__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list self.attribute_list = attribute_list
self.value = value self.value = value
...@@ -184,7 +193,7 @@ class Import(NodeBase): ...@@ -184,7 +193,7 @@ class Import(NodeBase):
def __init__(self, attribute_list, import_filename, **kwargs): def __init__(self, attribute_list, import_filename, **kwargs):
assert attribute_list is None or isinstance(attribute_list, AttributeList) assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert isinstance(import_filename, str) assert _IsStrOrUnicode(import_filename)
super(Import, self).__init__(**kwargs) super(Import, self).__init__(**kwargs)
self.attribute_list = attribute_list self.attribute_list = attribute_list
self.import_filename = import_filename self.import_filename = import_filename
...@@ -305,10 +314,10 @@ class Parameter(NodeBase): ...@@ -305,10 +314,10 @@ class Parameter(NodeBase):
"""Represents a method request or response parameter.""" """Represents a method request or response parameter."""
def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs): def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
assert isinstance(mojom_name, str) assert _IsStrOrUnicode(mojom_name)
assert attribute_list is None or isinstance(attribute_list, AttributeList) assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal) assert ordinal is None or isinstance(ordinal, Ordinal)
assert isinstance(typename, str) assert _IsStrOrUnicode(typename)
super(Parameter, self).__init__(**kwargs) super(Parameter, self).__init__(**kwargs)
self.mojom_name = mojom_name self.mojom_name = mojom_name
self.attribute_list = attribute_list self.attribute_list = attribute_list
...@@ -350,13 +359,14 @@ class StructField(Definition): ...@@ -350,13 +359,14 @@ class StructField(Definition):
def __init__(self, mojom_name, attribute_list, ordinal, typename, def __init__(self, mojom_name, attribute_list, ordinal, typename,
default_value, **kwargs): default_value, **kwargs):
assert isinstance(mojom_name, str) assert _IsStrOrUnicode(mojom_name)
assert attribute_list is None or isinstance(attribute_list, AttributeList) assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal) assert ordinal is None or isinstance(ordinal, Ordinal)
assert isinstance(typename, str) assert _IsStrOrUnicode(typename)
# The optional default value is currently either a value as a string or a # The optional default value is currently either a value as a string or a
# "wrapped identifier". # "wrapped identifier".
assert default_value is None or isinstance(default_value, (str, tuple)) assert default_value is None or _IsStrOrUnicode(default_value) or \
isinstance(default_value, tuple)
super(StructField, self).__init__(mojom_name, **kwargs) super(StructField, self).__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list self.attribute_list = attribute_list
self.ordinal = ordinal self.ordinal = ordinal
...@@ -396,10 +406,10 @@ class Union(Definition): ...@@ -396,10 +406,10 @@ class Union(Definition):
class UnionField(Definition): class UnionField(Definition):
def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs): def __init__(self, mojom_name, attribute_list, ordinal, typename, **kwargs):
assert isinstance(mojom_name, str) assert _IsStrOrUnicode(mojom_name)
assert attribute_list is None or isinstance(attribute_list, AttributeList) assert attribute_list is None or isinstance(attribute_list, AttributeList)
assert ordinal is None or isinstance(ordinal, Ordinal) assert ordinal is None or isinstance(ordinal, Ordinal)
assert isinstance(typename, str) assert _IsStrOrUnicode(typename)
super(UnionField, self).__init__(mojom_name, **kwargs) super(UnionField, self).__init__(mojom_name, **kwargs)
self.attribute_list = attribute_list self.attribute_list = attribute_list
self.ordinal = ordinal self.ordinal = ordinal
......
...@@ -472,7 +472,7 @@ def Parse(source, filename): ...@@ -472,7 +472,7 @@ def Parse(source, filename):
"""Parse source file to AST. """Parse source file to AST.
Args: Args:
source: The source text as a str. source: The source text as a str (Python 2 or 3) or unicode (Python 2).
filename: The filename that |source| originates from. filename: The filename that |source| originates from.
Returns: Returns:
......
...@@ -11,6 +11,7 @@ generate usable language bindings. ...@@ -11,6 +11,7 @@ generate usable language bindings.
""" """
import argparse import argparse
import codecs
import errno import errno
import json import json
import os import os
...@@ -193,7 +194,7 @@ def _ParseMojoms(mojom_files, ...@@ -193,7 +194,7 @@ def _ParseMojoms(mojom_files,
abs_paths = dict( abs_paths = dict(
(path, abs_path) for abs_path, path in mojom_files_to_parse.items()) (path, abs_path) for abs_path, path in mojom_files_to_parse.items())
for mojom_abspath, _ in mojom_files_to_parse.items(): for mojom_abspath, _ in mojom_files_to_parse.items():
with open(mojom_abspath) as f: with codecs.open(mojom_abspath, encoding='utf-8') as f:
ast = parser.Parse(''.join(f.readlines()), mojom_abspath) ast = parser.Parse(''.join(f.readlines()), mojom_abspath)
conditional_features.RemoveDisabledDefinitions(ast, enabled_features) conditional_features.RemoveDisabledDefinitions(ast, enabled_features)
loaded_mojom_asts[mojom_abspath] = ast loaded_mojom_asts[mojom_abspath] = ast
......
...@@ -22,7 +22,7 @@ class StableAttributeTest(MojomParserTestCase): ...@@ -22,7 +22,7 @@ class StableAttributeTest(MojomParserTestCase):
struct UnstableStruct { UnstableEnum a; }; struct UnstableStruct { UnstableEnum a; };
[Stable] union TestUnion { TestEnum a; TestStruct b; }; [Stable] union TestUnion { TestEnum a; TestStruct b; };
union UnstableUnion { UnstableEnum a; UnstableStruct b; }; union UnstableUnion { UnstableEnum a; UnstableStruct b; };
[Stable] interface TestInterface { Foo(TestUnion x) => (); }; [Stable] interface TestInterface { Foo@0(TestUnion x) => (); };
interface UnstableInterface { Foo(UnstableUnion x) => (); }; interface UnstableInterface { Foo(UnstableUnion x) => (); };
""") """)
self.ParseMojoms([mojom]) self.ParseMojoms([mojom])
...@@ -101,22 +101,27 @@ class StableAttributeTest(MojomParserTestCase): ...@@ -101,22 +101,27 @@ class StableAttributeTest(MojomParserTestCase):
"""A [Stable] interface is valid if all its methods' parameter types are """A [Stable] interface is valid if all its methods' parameter types are
stable, including response parameters where applicable.""" stable, including response parameters where applicable."""
self.ExtractTypes('[Stable] interface F {};') self.ExtractTypes('[Stable] interface F {};')
self.ExtractTypes('[Stable] interface F { A(int32 x); };') self.ExtractTypes('[Stable] interface F { A@0(int32 x); };')
self.ExtractTypes('[Stable] interface F { A(int32 x) => (bool b); };') self.ExtractTypes('[Stable] interface F { A@0(int32 x) => (bool b); };')
self.ExtractTypes("""\ self.ExtractTypes("""\
[Stable] enum E { A, B, C }; [Stable] enum E { A, B, C };
[Stable] struct S {}; [Stable] struct S {};
[Stable] interface F { A(E e, S s) => (bool b, array<S> s); }; [Stable] interface F { A@0(E e, S s) => (bool b, array<S> s); };
""") """)
with self.assertRaisesRegexp(Exception, 'because it depends on E'): with self.assertRaisesRegexp(Exception, 'because it depends on E'):
self.ExtractTypes('enum E { A, B, C }; [Stable] interface F { A(E e); };') self.ExtractTypes(
'enum E { A, B, C }; [Stable] interface F { A@0(E e); };')
with self.assertRaisesRegexp(Exception, 'because it depends on E'): with self.assertRaisesRegexp(Exception, 'because it depends on E'):
self.ExtractTypes( self.ExtractTypes(
'enum E { A, B, C }; [Stable] interface F { A(int32 x) => (E e); };') 'enum E { A, B, C }; [Stable] interface F { A@0(int32 x) => (E e); };'
)
with self.assertRaisesRegexp(Exception, 'because it depends on S'): with self.assertRaisesRegexp(Exception, 'because it depends on S'):
self.ExtractTypes( self.ExtractTypes(
'struct S {}; [Stable] interface F { A(int32 x) => (S s); };') 'struct S {}; [Stable] interface F { A@0(int32 x) => (S s); };')
with self.assertRaisesRegexp(Exception, 'because it depends on S'): with self.assertRaisesRegexp(Exception, 'because it depends on S'):
self.ExtractTypes( self.ExtractTypes(
'struct S {}; [Stable] interface F { A(S s) => (bool b); };') 'struct S {}; [Stable] interface F { A@0(S s) => (bool b); };')
with self.assertRaisesRegexp(Exception, 'explicit method ordinals'):
self.ExtractTypes('[Stable] interface F { A() => (); };')
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