An attempt at getting image data back
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Validate mojo attributes are allowed in Chrome before generation."""
|
||||
|
||||
import mojom.generate.check as check
|
||||
import mojom.generate.module as module
|
||||
|
||||
_COMMON_ATTRIBUTES = {
|
||||
'EnableIf',
|
||||
'EnableIfNot',
|
||||
}
|
||||
|
||||
# For struct, union & parameter lists.
|
||||
_COMMON_FIELD_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'MinVersion',
|
||||
'RenamedFrom',
|
||||
}
|
||||
|
||||
# Note: `Default`` goes on the default _value_, not on the enum.
|
||||
# Note: [Stable] without [Extensible] is not allowed.
|
||||
_ENUM_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'Extensible',
|
||||
'Native',
|
||||
'Stable',
|
||||
'RenamedFrom',
|
||||
'Uuid',
|
||||
}
|
||||
|
||||
# TODO(crbug.com/1234883) MinVersion is not needed for EnumVal.
|
||||
_ENUMVAL_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'Default',
|
||||
'MinVersion',
|
||||
}
|
||||
|
||||
_INTERFACE_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'RenamedFrom',
|
||||
'RequireContext',
|
||||
'RuntimeFeature',
|
||||
'ServiceSandbox',
|
||||
'Stable',
|
||||
'Uuid',
|
||||
}
|
||||
|
||||
_METHOD_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'AllowedContext',
|
||||
'MinVersion',
|
||||
'NoInterrupt',
|
||||
'RuntimeFeature',
|
||||
'SupportsUrgent',
|
||||
'Sync',
|
||||
'UnlimitedSize',
|
||||
}
|
||||
|
||||
_MODULE_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'JavaConstantsClassName',
|
||||
'JavaPackage',
|
||||
}
|
||||
|
||||
_PARAMETER_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES
|
||||
|
||||
_STRUCT_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'CustomSerializer',
|
||||
'JavaClassName',
|
||||
'Native',
|
||||
'Stable',
|
||||
'RenamedFrom',
|
||||
'Uuid',
|
||||
}
|
||||
|
||||
_STRUCT_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES
|
||||
|
||||
_UNION_ATTRIBUTES = _COMMON_ATTRIBUTES | {
|
||||
'Extensible',
|
||||
'Stable',
|
||||
'RenamedFrom',
|
||||
'Uuid',
|
||||
}
|
||||
|
||||
_UNION_FIELD_ATTRIBUTES = _COMMON_FIELD_ATTRIBUTES | {
|
||||
'Default',
|
||||
}
|
||||
|
||||
# TODO(https://crbug.com/1193875) empty this set and remove the allowlist.
|
||||
_STABLE_ONLY_ALLOWLISTED_ENUMS = {
|
||||
'crosapi.mojom.OptionalBool',
|
||||
'crosapi.mojom.TriState',
|
||||
}
|
||||
|
||||
|
||||
class Check(check.Check):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Check, self).__init__(*args, **kwargs)
|
||||
|
||||
def _Respell(self, allowed, attribute):
|
||||
for a in allowed:
|
||||
if a.lower() == attribute.lower():
|
||||
return f" - Did you mean: {a}?"
|
||||
return ""
|
||||
|
||||
def _CheckAttributes(self, context, allowed, attributes):
|
||||
if not attributes:
|
||||
return
|
||||
for attribute in attributes:
|
||||
if not attribute in allowed:
|
||||
# Is there a close misspelling?
|
||||
hint = self._Respell(allowed, attribute)
|
||||
raise check.CheckException(
|
||||
self.module,
|
||||
f"attribute {attribute} not allowed on {context}{hint}")
|
||||
|
||||
def _CheckEnumAttributes(self, enum):
|
||||
if enum.attributes:
|
||||
self._CheckAttributes("enum", _ENUM_ATTRIBUTES, enum.attributes)
|
||||
if 'Stable' in enum.attributes and not 'Extensible' in enum.attributes:
|
||||
full_name = f"{self.module.mojom_namespace}.{enum.mojom_name}"
|
||||
if full_name not in _STABLE_ONLY_ALLOWLISTED_ENUMS:
|
||||
raise check.CheckException(
|
||||
self.module,
|
||||
f"[Extensible] required on [Stable] enum {full_name}")
|
||||
for enumval in enum.fields:
|
||||
self._CheckAttributes("enum value", _ENUMVAL_ATTRIBUTES,
|
||||
enumval.attributes)
|
||||
|
||||
def _CheckInterfaceAttributes(self, interface):
|
||||
self._CheckAttributes("interface", _INTERFACE_ATTRIBUTES,
|
||||
interface.attributes)
|
||||
for method in interface.methods:
|
||||
self._CheckAttributes("method", _METHOD_ATTRIBUTES, method.attributes)
|
||||
for param in method.parameters:
|
||||
self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES,
|
||||
param.attributes)
|
||||
if method.response_parameters:
|
||||
for param in method.response_parameters:
|
||||
self._CheckAttributes("parameter", _PARAMETER_ATTRIBUTES,
|
||||
param.attributes)
|
||||
for enum in interface.enums:
|
||||
self._CheckEnumAttributes(enum)
|
||||
|
||||
def _CheckModuleAttributes(self):
|
||||
self._CheckAttributes("module", _MODULE_ATTRIBUTES, self.module.attributes)
|
||||
|
||||
def _CheckStructAttributes(self, struct):
|
||||
self._CheckAttributes("struct", _STRUCT_ATTRIBUTES, struct.attributes)
|
||||
for field in struct.fields:
|
||||
self._CheckAttributes("struct field", _STRUCT_FIELD_ATTRIBUTES,
|
||||
field.attributes)
|
||||
for enum in struct.enums:
|
||||
self._CheckEnumAttributes(enum)
|
||||
|
||||
def _CheckUnionAttributes(self, union):
|
||||
self._CheckAttributes("union", _UNION_ATTRIBUTES, union.attributes)
|
||||
for field in union.fields:
|
||||
self._CheckAttributes("union field", _UNION_FIELD_ATTRIBUTES,
|
||||
field.attributes)
|
||||
|
||||
def CheckModule(self):
|
||||
"""Note that duplicate attributes are forbidden at the parse phase.
|
||||
We also do not need to look at the types of any parameters, as they will be
|
||||
checked where they are defined. Consts do not have attributes so can be
|
||||
skipped."""
|
||||
self._CheckModuleAttributes()
|
||||
for interface in self.module.interfaces:
|
||||
self._CheckInterfaceAttributes(interface)
|
||||
for enum in self.module.enums:
|
||||
self._CheckEnumAttributes(enum)
|
||||
for struct in self.module.structs:
|
||||
self._CheckStructAttributes(struct)
|
||||
for union in self.module.unions:
|
||||
self._CheckUnionAttributes(union)
|
||||
@@ -0,0 +1,194 @@
|
||||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import unittest
|
||||
|
||||
import mojom.generate.check as check
|
||||
from mojom_bindings_generator import LoadChecks, _Generate
|
||||
from mojom_parser_test_case import MojomParserTestCase
|
||||
|
||||
|
||||
class FakeArgs:
|
||||
"""Fakes args to _Generate - intention is to do just enough to run checks"""
|
||||
|
||||
def __init__(self, tester, files=None):
|
||||
""" `tester` is MojomParserTestCase for paths.
|
||||
`files` will have tester path added."""
|
||||
self.checks_string = 'attributes'
|
||||
self.depth = tester.GetPath('')
|
||||
self.filelist = None
|
||||
self.filename = [tester.GetPath(x) for x in files]
|
||||
self.gen_directories = tester.GetPath('gen')
|
||||
self.generators_string = ''
|
||||
self.import_directories = []
|
||||
self.output_dir = tester.GetPath('out')
|
||||
self.scrambled_message_id_salt_paths = None
|
||||
self.typemaps = []
|
||||
self.variant = 'none'
|
||||
|
||||
|
||||
class MojoBindingsCheckTest(MojomParserTestCase):
|
||||
def _ParseAndGenerate(self, mojoms):
|
||||
self.ParseMojoms(mojoms)
|
||||
args = FakeArgs(self, files=mojoms)
|
||||
_Generate(args, {})
|
||||
|
||||
def _testValid(self, filename, content):
|
||||
self.WriteFile(filename, content)
|
||||
self._ParseAndGenerate([filename])
|
||||
|
||||
def _testThrows(self, filename, content, regexp):
|
||||
mojoms = []
|
||||
self.WriteFile(filename, content)
|
||||
mojoms.append(filename)
|
||||
with self.assertRaisesRegexp(check.CheckException, regexp):
|
||||
self._ParseAndGenerate(mojoms)
|
||||
|
||||
def testLoads(self):
|
||||
"""Validate that the check is registered under the expected name."""
|
||||
check_modules = LoadChecks('attributes')
|
||||
self.assertTrue(check_modules['attributes'])
|
||||
|
||||
def testNoAnnotations(self):
|
||||
# Undecorated mojom should be fine.
|
||||
self._testValid(
|
||||
"a.mojom", """
|
||||
module a;
|
||||
struct Bar { int32 a; };
|
||||
enum Hello { kValue };
|
||||
union Thingy { Bar b; Hello hi; };
|
||||
interface Foo {
|
||||
Foo(int32 a, Hello hi, Thingy t) => (Bar b);
|
||||
};
|
||||
""")
|
||||
|
||||
def testValidAnnotations(self):
|
||||
# Obviously this is meaningless and won't generate, but it should pass
|
||||
# the attribute check's validation.
|
||||
self._testValid(
|
||||
"a.mojom", """
|
||||
[JavaConstantsClassName="FakeClass",JavaPackage="org.chromium.Fake"]
|
||||
module a;
|
||||
[Stable, Extensible]
|
||||
enum Hello { [Default] kValue, kValue2, [MinVersion=2] kValue3 };
|
||||
[Native]
|
||||
enum NativeEnum {};
|
||||
[Stable,Extensible]
|
||||
union Thingy { Bar b; [Default]int32 c; Hello hi; };
|
||||
|
||||
[Stable,RenamedFrom="module.other.Foo",
|
||||
Uuid="4C178401-4B07-4C2E-9255-5401A943D0C7"]
|
||||
struct Structure { Hello hi; };
|
||||
|
||||
[ServiceSandbox=Hello.kValue,RequireContext=Hello.kValue,Stable,
|
||||
Uuid="2F17D7DD-865A-4B1C-9394-9C94E035E82F"]
|
||||
interface Foo {
|
||||
[AllowedContext=Hello.kValue]
|
||||
Foo@0(int32 a) => (int32 b);
|
||||
[MinVersion=2,Sync,UnlimitedSize,NoInterrupt]
|
||||
Bar@1(int32 b, [MinVersion=2]Structure? s) => (bool c);
|
||||
};
|
||||
|
||||
[RuntimeFeature=test.mojom.FeatureName]
|
||||
interface FooFeatureControlled {};
|
||||
|
||||
interface FooMethodFeatureControlled {
|
||||
[RuntimeFeature=test.mojom.FeatureName]
|
||||
MethodWithFeature() => (bool c);
|
||||
};
|
||||
""")
|
||||
|
||||
def testWrongModuleStable(self):
|
||||
contents = """
|
||||
// err: module cannot be Stable
|
||||
[Stable]
|
||||
module a;
|
||||
enum Hello { kValue, kValue2, kValue3 };
|
||||
enum NativeEnum {};
|
||||
struct Structure { Hello hi; };
|
||||
|
||||
interface Foo {
|
||||
Foo(int32 a) => (int32 b);
|
||||
Bar(int32 b, Structure? s) => (bool c);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'attribute Stable not allowed on module')
|
||||
|
||||
def testWrongEnumDefault(self):
|
||||
contents = """
|
||||
module a;
|
||||
// err: default should go on EnumValue not Enum.
|
||||
[Default=kValue]
|
||||
enum Hello { kValue, kValue2, kValue3 };
|
||||
enum NativeEnum {};
|
||||
struct Structure { Hello hi; };
|
||||
|
||||
interface Foo {
|
||||
Foo(int32 a) => (int32 b);
|
||||
Bar(int32 b, Structure? s) => (bool c);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'attribute Default not allowed on enum')
|
||||
|
||||
def testWrongStructMinVersion(self):
|
||||
contents = """
|
||||
module a;
|
||||
enum Hello { kValue, kValue2, kValue3 };
|
||||
enum NativeEnum {};
|
||||
// err: struct cannot have MinVersion.
|
||||
[MinVersion=2]
|
||||
struct Structure { Hello hi; };
|
||||
|
||||
interface Foo {
|
||||
Foo(int32 a) => (int32 b);
|
||||
Bar(int32 b, Structure? s) => (bool c);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'attribute MinVersion not allowed on struct')
|
||||
|
||||
def testWrongMethodRequireContext(self):
|
||||
contents = """
|
||||
module a;
|
||||
enum Hello { kValue, kValue2, kValue3 };
|
||||
enum NativeEnum {};
|
||||
struct Structure { Hello hi; };
|
||||
|
||||
interface Foo {
|
||||
// err: RequireContext is for interfaces.
|
||||
[RequireContext=Hello.kValue]
|
||||
Foo(int32 a) => (int32 b);
|
||||
Bar(int32 b, Structure? s) => (bool c);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'RequireContext not allowed on method')
|
||||
|
||||
def testWrongMethodRequireContext(self):
|
||||
# crbug.com/1230122
|
||||
contents = """
|
||||
module a;
|
||||
interface Foo {
|
||||
// err: sync not Sync.
|
||||
[sync]
|
||||
Foo(int32 a) => (int32 b);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'attribute sync not allowed.*Did you mean: Sync')
|
||||
|
||||
def testStableExtensibleEnum(self):
|
||||
# crbug.com/1193875
|
||||
contents = """
|
||||
module a;
|
||||
[Stable]
|
||||
enum Foo {
|
||||
kDefaultVal,
|
||||
kOtherVal = 2,
|
||||
};
|
||||
"""
|
||||
self._testThrows('a.mojom', contents,
|
||||
'Extensible.*?required.*?Stable.*?enum')
|
||||
@@ -0,0 +1,34 @@
|
||||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Ensure no duplicate type definitions before generation."""
|
||||
|
||||
import mojom.generate.check as check
|
||||
import mojom.generate.module as module
|
||||
|
||||
|
||||
class Check(check.Check):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Check, self).__init__(*args, **kwargs)
|
||||
|
||||
def CheckModule(self):
|
||||
kinds = dict()
|
||||
for module in self.module.imports:
|
||||
for kind in module.enums + module.structs + module.unions:
|
||||
kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'
|
||||
if kind_name in kinds:
|
||||
previous_module = kinds[kind_name]
|
||||
if previous_module.path != module.path:
|
||||
raise check.CheckException(
|
||||
self.module, f"multiple-definition for type {kind_name}" +
|
||||
f"(defined in both {previous_module} and {module})")
|
||||
kinds[kind_name] = kind.module
|
||||
|
||||
for kind in self.module.enums + self.module.structs + self.module.unions:
|
||||
kind_name = f'{kind.module.mojom_namespace}.{kind.mojom_name}'
|
||||
if kind_name in kinds:
|
||||
previous_module = kinds[kind_name]
|
||||
raise check.CheckException(
|
||||
self.module, f"multiple-definition for type {kind_name}" +
|
||||
f"(previous definition in {previous_module})")
|
||||
return True
|
||||
@@ -0,0 +1,62 @@
|
||||
# Copyright 2023 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Validate mojo runtime feature guarded interfaces are nullable."""
|
||||
|
||||
import mojom.generate.check as check
|
||||
import mojom.generate.module as module
|
||||
|
||||
|
||||
class Check(check.Check):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(Check, self).__init__(*args, **kwargs)
|
||||
|
||||
# `param` is an Interface of some sort.
|
||||
def _CheckNonNullableFeatureGuardedInterface(self, kind):
|
||||
# Only need to validate interface if it has a RuntimeFeature
|
||||
if not kind.kind.runtime_feature:
|
||||
return
|
||||
# Nullable (optional) is ok as the interface expects they might not be sent.
|
||||
if kind.is_nullable:
|
||||
return
|
||||
interface = kind.kind.mojom_name
|
||||
raise check.CheckException(
|
||||
self.module,
|
||||
f"interface {interface} has a RuntimeFeature but is not nullable")
|
||||
|
||||
# `param` can be a lot of things so check if it is a remote/receiver.
|
||||
# Array/Map must be recursed into.
|
||||
def _CheckFieldOrParam(self, kind):
|
||||
if module.IsAnyInterfaceKind(kind):
|
||||
self._CheckNonNullableFeatureGuardedInterface(kind)
|
||||
if module.IsArrayKind(kind):
|
||||
self._CheckFieldOrParam(kind.kind)
|
||||
if module.IsMapKind(kind):
|
||||
self._CheckFieldOrParam(kind.key_kind)
|
||||
self._CheckFieldOrParam(kind.value_kind)
|
||||
|
||||
def _CheckInterfaceFeatures(self, interface):
|
||||
for method in interface.methods:
|
||||
for param in method.parameters:
|
||||
self._CheckFieldOrParam(param.kind)
|
||||
if method.response_parameters:
|
||||
for param in method.response_parameters:
|
||||
self._CheckFieldOrParam(param.kind)
|
||||
|
||||
def _CheckStructFeatures(self, struct):
|
||||
for field in struct.fields:
|
||||
self._CheckFieldOrParam(field.kind)
|
||||
|
||||
def _CheckUnionFeatures(self, union):
|
||||
for field in union.fields:
|
||||
self._CheckFieldOrParam(field.kind)
|
||||
|
||||
def CheckModule(self):
|
||||
"""Validate that any runtime feature guarded interfaces that might be passed
|
||||
over mojo are nullable."""
|
||||
for interface in self.module.interfaces:
|
||||
self._CheckInterfaceFeatures(interface)
|
||||
for struct in self.module.structs:
|
||||
self._CheckStructFeatures(struct)
|
||||
for union in self.module.unions:
|
||||
self._CheckUnionFeatures(union)
|
||||
@@ -0,0 +1,173 @@
|
||||
# Copyright 2023 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import unittest
|
||||
|
||||
import mojom.generate.check as check
|
||||
from mojom_bindings_generator import LoadChecks, _Generate
|
||||
from mojom_parser_test_case import MojomParserTestCase
|
||||
|
||||
|
||||
class FakeArgs:
|
||||
"""Fakes args to _Generate - intention is to do just enough to run checks"""
|
||||
def __init__(self, tester, files=None):
|
||||
""" `tester` is MojomParserTestCase for paths.
|
||||
`files` will have tester path added."""
|
||||
self.checks_string = 'features'
|
||||
self.depth = tester.GetPath('')
|
||||
self.filelist = None
|
||||
self.filename = [tester.GetPath(x) for x in files]
|
||||
self.gen_directories = tester.GetPath('gen')
|
||||
self.generators_string = ''
|
||||
self.import_directories = []
|
||||
self.output_dir = tester.GetPath('out')
|
||||
self.scrambled_message_id_salt_paths = None
|
||||
self.typemaps = []
|
||||
self.variant = 'none'
|
||||
|
||||
|
||||
class MojoBindingsCheckTest(MojomParserTestCase):
|
||||
def _ParseAndGenerate(self, mojoms):
|
||||
self.ParseMojoms(mojoms)
|
||||
args = FakeArgs(self, files=mojoms)
|
||||
_Generate(args, {})
|
||||
|
||||
def assertValid(self, filename, content):
|
||||
self.WriteFile(filename, content)
|
||||
self._ParseAndGenerate([filename])
|
||||
|
||||
def assertThrows(self, filename, content, regexp):
|
||||
mojoms = []
|
||||
self.WriteFile(filename, content)
|
||||
mojoms.append(filename)
|
||||
with self.assertRaisesRegexp(check.CheckException, regexp):
|
||||
self._ParseAndGenerate(mojoms)
|
||||
|
||||
def testLoads(self):
|
||||
"""Validate that the check is registered under the expected name."""
|
||||
check_modules = LoadChecks('features')
|
||||
self.assertTrue(check_modules['features'])
|
||||
|
||||
def testNullableOk(self):
|
||||
self.assertValid(
|
||||
"a.mojom", """
|
||||
module a;
|
||||
// Scaffolding.
|
||||
feature kFeature {
|
||||
const string name = "Hello";
|
||||
const bool enabled_state = false;
|
||||
};
|
||||
[RuntimeFeature=kFeature]
|
||||
interface Guarded {
|
||||
};
|
||||
|
||||
// Unguarded interfaces should be ok everywhere.
|
||||
interface NotGuarded { };
|
||||
|
||||
// Optional (nullable) interfaces should be ok everywhere:
|
||||
struct Bar {
|
||||
pending_remote<Guarded>? remote;
|
||||
pending_receiver<Guarded>? receiver;
|
||||
};
|
||||
union Thingy {
|
||||
pending_remote<Guarded>? remote;
|
||||
pending_receiver<Guarded>? receiver;
|
||||
};
|
||||
interface Foo {
|
||||
Foo(
|
||||
pending_remote<Guarded>? remote,
|
||||
pending_receiver<Guarded>? receiver,
|
||||
pending_associated_remote<Guarded>? a_remote,
|
||||
pending_associated_receiver<Guarded>? a_receiver,
|
||||
// Unguarded interfaces do not have to be nullable.
|
||||
pending_remote<NotGuarded> remote,
|
||||
pending_receiver<NotGuarded> receiver,
|
||||
pending_associated_remote<NotGuarded> a_remote,
|
||||
pending_associated_receiver<NotGuarded> a_receiver
|
||||
) => (
|
||||
pending_remote<Guarded>? remote,
|
||||
pending_receiver<Guarded>? receiver
|
||||
);
|
||||
Bar(array<pending_remote<Guarded>?> remote)
|
||||
=> (map<string, pending_receiver<Guarded>?> a);
|
||||
};
|
||||
""")
|
||||
|
||||
def testMethodParamsMustBeNullable(self):
|
||||
prelude = """
|
||||
module a;
|
||||
// Scaffolding.
|
||||
feature kFeature {
|
||||
const string name = "Hello";
|
||||
const bool enabled_state = false;
|
||||
};
|
||||
[RuntimeFeature=kFeature]
|
||||
interface Guarded { };
|
||||
"""
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(pending_remote<Guarded> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(bool foo) => (pending_receiver<Guarded> a);
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(pending_receiver<Guarded> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(pending_associated_remote<Guarded> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(pending_associated_receiver<Guarded> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(array<pending_associated_receiver<Guarded>> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
interface Trial {
|
||||
Method(map<string, pending_associated_receiver<Guarded>> a) => ();
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
|
||||
def testStructUnionMembersMustBeNullable(self):
|
||||
prelude = """
|
||||
module a;
|
||||
// Scaffolding.
|
||||
feature kFeature {
|
||||
const string name = "Hello";
|
||||
const bool enabled_state = false;
|
||||
};
|
||||
[RuntimeFeature=kFeature]
|
||||
interface Guarded { };
|
||||
"""
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
struct Trial {
|
||||
pending_remote<Guarded> a;
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
self.assertThrows(
|
||||
'a.mojom', prelude + """
|
||||
union Trial {
|
||||
pending_remote<Guarded> a;
|
||||
};
|
||||
""", 'interface Guarded has a RuntimeFeature')
|
||||
@@ -0,0 +1,102 @@
|
||||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
"""Validate RequireContext and AllowedContext annotations before generation."""
|
||||
|
||||
import mojom.generate.check as check
|
||||
import mojom.generate.module as module
|
||||
|
||||
|
||||
class Check(check.Check):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.kind_to_interfaces = dict()
|
||||
super(Check, self).__init__(*args, **kwargs)
|
||||
|
||||
def _IsPassedInterface(self, candidate):
|
||||
if isinstance(
|
||||
candidate.kind,
|
||||
(module.PendingReceiver, module.PendingRemote,
|
||||
module.PendingAssociatedReceiver, module.PendingAssociatedRemote)):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _CheckInterface(self, method, param):
|
||||
# |param| is a pending_x<Interface> so need .kind.kind to get Interface.
|
||||
interface = param.kind.kind
|
||||
if interface.require_context:
|
||||
if method.allowed_context is None:
|
||||
raise check.CheckException(
|
||||
self.module, "method `{}` has parameter `{}` which passes interface"
|
||||
" `{}` that requires an AllowedContext annotation but none exists.".
|
||||
format(
|
||||
method.mojom_name,
|
||||
param.mojom_name,
|
||||
interface.mojom_name,
|
||||
))
|
||||
# If a string was provided, or if an enum was not imported, this will
|
||||
# be a string and we cannot validate that it is in range.
|
||||
if not isinstance(method.allowed_context, module.EnumValue):
|
||||
raise check.CheckException(
|
||||
self.module,
|
||||
"method `{}` has AllowedContext={} which is not a valid enum value."
|
||||
.format(method.mojom_name, method.allowed_context))
|
||||
# EnumValue must be from the same enum to be compared.
|
||||
if interface.require_context.enum != method.allowed_context.enum:
|
||||
raise check.CheckException(
|
||||
self.module, "method `{}` has parameter `{}` which passes interface"
|
||||
" `{}` that requires AllowedContext={} but one of kind `{}` was "
|
||||
"provided.".format(
|
||||
method.mojom_name,
|
||||
param.mojom_name,
|
||||
interface.mojom_name,
|
||||
interface.require_context.enum,
|
||||
method.allowed_context.enum,
|
||||
))
|
||||
# RestrictContext enums have most privileged field first (lowest value).
|
||||
interface_value = interface.require_context.field.numeric_value
|
||||
method_value = method.allowed_context.field.numeric_value
|
||||
if interface_value < method_value:
|
||||
raise check.CheckException(
|
||||
self.module, "RequireContext={} > AllowedContext={} for method "
|
||||
"`{}` which passes interface `{}`.".format(
|
||||
interface.require_context.GetSpec(),
|
||||
method.allowed_context.GetSpec(), method.mojom_name,
|
||||
interface.mojom_name))
|
||||
return True
|
||||
|
||||
def _GatherReferencedInterfaces(self, field):
|
||||
key = field.kind.spec
|
||||
# structs/unions can nest themselves so we need to bookkeep.
|
||||
if not key in self.kind_to_interfaces:
|
||||
# Might reference ourselves so have to create the list first.
|
||||
self.kind_to_interfaces[key] = set()
|
||||
for param in field.kind.fields:
|
||||
if self._IsPassedInterface(param):
|
||||
self.kind_to_interfaces[key].add(param)
|
||||
elif isinstance(param.kind, (module.Struct, module.Union)):
|
||||
for iface in self._GatherReferencedInterfaces(param):
|
||||
self.kind_to_interfaces[key].add(iface)
|
||||
return self.kind_to_interfaces[key]
|
||||
|
||||
def _CheckParams(self, method, params):
|
||||
# Note: we have to repeat _CheckParams for each method as each might have
|
||||
# different AllowedContext= attributes. We cannot memoize this function,
|
||||
# but can do so for gathering referenced interfaces as their RequireContext
|
||||
# attributes do not change.
|
||||
for param in params:
|
||||
if self._IsPassedInterface(param):
|
||||
self._CheckInterface(method, param)
|
||||
elif isinstance(param.kind, (module.Struct, module.Union)):
|
||||
for interface in self._GatherReferencedInterfaces(param):
|
||||
self._CheckInterface(method, interface)
|
||||
|
||||
def _CheckMethod(self, method):
|
||||
if method.parameters:
|
||||
self._CheckParams(method, method.parameters)
|
||||
if method.response_parameters:
|
||||
self._CheckParams(method, method.response_parameters)
|
||||
|
||||
def CheckModule(self):
|
||||
for interface in self.module.interfaces:
|
||||
for method in interface.methods:
|
||||
self._CheckMethod(method)
|
||||
@@ -0,0 +1,254 @@
|
||||
# Copyright 2022 The Chromium Authors
|
||||
# Use of this source code is governed by a BSD-style license that can be
|
||||
# found in the LICENSE file.
|
||||
|
||||
import unittest
|
||||
|
||||
import mojom.generate.check as check
|
||||
from mojom_bindings_generator import LoadChecks, _Generate
|
||||
from mojom_parser_test_case import MojomParserTestCase
|
||||
|
||||
# Mojoms that we will use in multiple tests.
|
||||
basic_mojoms = {
|
||||
'level.mojom':
|
||||
"""
|
||||
module level;
|
||||
enum Level {
|
||||
kHighest,
|
||||
kMiddle,
|
||||
kLowest,
|
||||
};
|
||||
""",
|
||||
'interfaces.mojom':
|
||||
"""
|
||||
module interfaces;
|
||||
import "level.mojom";
|
||||
struct Foo {int32 bar;};
|
||||
[RequireContext=level.Level.kHighest]
|
||||
interface High {
|
||||
DoFoo(Foo foo);
|
||||
};
|
||||
[RequireContext=level.Level.kMiddle]
|
||||
interface Mid {
|
||||
DoFoo(Foo foo);
|
||||
};
|
||||
[RequireContext=level.Level.kLowest]
|
||||
interface Low {
|
||||
DoFoo(Foo foo);
|
||||
};
|
||||
"""
|
||||
}
|
||||
|
||||
|
||||
class FakeArgs:
|
||||
"""Fakes args to _Generate - intention is to do just enough to run checks"""
|
||||
|
||||
def __init__(self, tester, files=None):
|
||||
""" `tester` is MojomParserTestCase for paths.
|
||||
`files` will have tester path added."""
|
||||
self.checks_string = 'restrictions'
|
||||
self.depth = tester.GetPath('')
|
||||
self.filelist = None
|
||||
self.filename = [tester.GetPath(x) for x in files]
|
||||
self.gen_directories = tester.GetPath('gen')
|
||||
self.generators_string = ''
|
||||
self.import_directories = []
|
||||
self.output_dir = tester.GetPath('out')
|
||||
self.scrambled_message_id_salt_paths = None
|
||||
self.typemaps = []
|
||||
self.variant = 'none'
|
||||
|
||||
|
||||
class MojoBindingsCheckTest(MojomParserTestCase):
|
||||
def _WriteBasicMojoms(self):
|
||||
for filename, contents in basic_mojoms.items():
|
||||
self.WriteFile(filename, contents)
|
||||
return list(basic_mojoms.keys())
|
||||
|
||||
def _ParseAndGenerate(self, mojoms):
|
||||
self.ParseMojoms(mojoms)
|
||||
args = FakeArgs(self, files=mojoms)
|
||||
_Generate(args, {})
|
||||
|
||||
def testLoads(self):
|
||||
"""Validate that the check is registered under the expected name."""
|
||||
check_modules = LoadChecks('restrictions')
|
||||
self.assertTrue(check_modules['restrictions'])
|
||||
|
||||
def testValidAnnotations(self):
|
||||
mojoms = self._WriteBasicMojoms()
|
||||
|
||||
a = 'a.mojom'
|
||||
self.WriteFile(
|
||||
a, """
|
||||
module a;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
|
||||
interface PassesHigh {
|
||||
[AllowedContext=level.Level.kHighest]
|
||||
DoHigh(pending_receiver<interfaces.High> hi);
|
||||
};
|
||||
interface PassesMedium {
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoMedium(pending_receiver<interfaces.Mid> hi);
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoMediumRem(pending_remote<interfaces.Mid> hi);
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoMediumAssoc(pending_associated_receiver<interfaces.Mid> hi);
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoMediumAssocRem(pending_associated_remote<interfaces.Mid> hi);
|
||||
};
|
||||
interface PassesLow {
|
||||
[AllowedContext=level.Level.kLowest]
|
||||
DoLow(pending_receiver<interfaces.Low> hi);
|
||||
};
|
||||
|
||||
struct One { pending_receiver<interfaces.High> hi; };
|
||||
struct Two { One one; };
|
||||
interface PassesNestedHigh {
|
||||
[AllowedContext=level.Level.kHighest]
|
||||
DoNestedHigh(Two two);
|
||||
};
|
||||
|
||||
// Allowed as PassesHigh is not itself restricted.
|
||||
interface PassesPassesHigh {
|
||||
DoPass(pending_receiver<PassesHigh> hiho);
|
||||
};
|
||||
""")
|
||||
mojoms.append(a)
|
||||
self._ParseAndGenerate(mojoms)
|
||||
|
||||
def _testThrows(self, filename, content, regexp):
|
||||
mojoms = self._WriteBasicMojoms()
|
||||
self.WriteFile(filename, content)
|
||||
mojoms.append(filename)
|
||||
with self.assertRaisesRegexp(check.CheckException, regexp):
|
||||
self._ParseAndGenerate(mojoms)
|
||||
|
||||
def testMissingAnnotation(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
|
||||
interface PassesHigh {
|
||||
// err: missing annotation.
|
||||
DoHigh(pending_receiver<interfaces.High> hi);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
|
||||
|
||||
def testAllowTooLow(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
|
||||
interface PassesHigh {
|
||||
// err: level is worse than required.
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoHigh(pending_receiver<interfaces.High> hi);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')
|
||||
|
||||
def testWrongEnumInAllow(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
enum Blah {
|
||||
kZero,
|
||||
};
|
||||
interface PassesHigh {
|
||||
// err: different enums.
|
||||
[AllowedContext=Blah.kZero]
|
||||
DoHigh(pending_receiver<interfaces.High> hi);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents, 'but one of kind')
|
||||
|
||||
def testNotAnEnumInAllow(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
interface PassesHigh {
|
||||
// err: not an enum.
|
||||
[AllowedContext=doopdedoo.mojom.kWhatever]
|
||||
DoHigh(pending_receiver<interfaces.High> hi);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents, 'not a valid enum value')
|
||||
|
||||
def testMissingAllowedForNestedStructs(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
struct One { pending_receiver<interfaces.High> hi; };
|
||||
struct Two { One one; };
|
||||
interface PassesNestedHigh {
|
||||
// err: missing annotation.
|
||||
DoNestedHigh(Two two);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
|
||||
|
||||
def testMissingAllowedForNestedUnions(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
struct One { pending_receiver<interfaces.High> hi; };
|
||||
struct Two { One one; };
|
||||
union Three {One one; Two two; };
|
||||
interface PassesNestedHigh {
|
||||
// err: missing annotation.
|
||||
DoNestedHigh(Three three);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents, 'require.*?AllowedContext')
|
||||
|
||||
def testMultipleInterfacesThrows(self):
|
||||
contents = """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
struct One { pending_receiver<interfaces.High> hi; };
|
||||
interface PassesMultipleInterfaces {
|
||||
[AllowedContext=level.Level.kMiddle]
|
||||
DoMultiple(
|
||||
pending_remote<interfaces.Mid> mid,
|
||||
pending_receiver<interfaces.High> hi,
|
||||
One one
|
||||
);
|
||||
};
|
||||
"""
|
||||
self._testThrows('b.mojom', contents,
|
||||
'RequireContext=.*?kHighest > AllowedContext=.*?kMiddle')
|
||||
|
||||
def testMultipleInterfacesAllowed(self):
|
||||
"""Multiple interfaces can be passed, all satisfy the level."""
|
||||
mojoms = self._WriteBasicMojoms()
|
||||
|
||||
b = "b.mojom"
|
||||
self.WriteFile(
|
||||
b, """
|
||||
module b;
|
||||
import "level.mojom";
|
||||
import "interfaces.mojom";
|
||||
struct One { pending_receiver<interfaces.High> hi; };
|
||||
interface PassesMultipleInterfaces {
|
||||
[AllowedContext=level.Level.kHighest]
|
||||
DoMultiple(
|
||||
pending_receiver<interfaces.High> hi,
|
||||
pending_remote<interfaces.Mid> mid,
|
||||
One one
|
||||
);
|
||||
};
|
||||
""")
|
||||
mojoms.append(b)
|
||||
self._ParseAndGenerate(mojoms)
|
||||
Reference in New Issue
Block a user