Better error message for multiple defined symbols.

Test: nose2
Test: m ndk
Bug: http://b/116629622
Change-Id: I30719eaf29d63d8c6595bbab4e5214a1ce6189ca
This commit is contained in:
Dan Albert 2018-10-09 16:36:03 -07:00
parent f5b8184abe
commit 756f2d0e1b
2 changed files with 196 additions and 59 deletions

View File

@ -20,6 +20,7 @@ import json
import logging
import os
import re
import sys
ALL_ARCHITECTURES = (
@ -107,22 +108,33 @@ def version_is_private(version):
return version.endswith('_PRIVATE') or version.endswith('_PLATFORM')
def should_omit_version(name, tags, arch, api, vndk):
def should_omit_version(version, arch, api, vndk):
"""Returns True if the version section should be ommitted.
We want to omit any sections that do not have any symbols we'll have in the
stub library. Sections that contain entirely future symbols or only symbols
for certain architectures.
"""
if version_is_private(name):
if version_is_private(version.name):
return True
if 'platform-only' in tags:
if 'platform-only' in version.tags:
return True
if 'vndk' in tags and not vndk:
if 'vndk' in version.tags and not vndk:
return True
if not symbol_in_arch(tags, arch):
if not symbol_in_arch(version.tags, arch):
return True
if not symbol_in_api(tags, arch, api):
if not symbol_in_api(version.tags, arch, api):
return True
return False
def should_omit_symbol(symbol, arch, api, vndk):
"""Returns True if the symbol should be omitted."""
if not vndk and 'vndk' in symbol.tags:
return True
if not symbol_in_arch(symbol.tags, arch):
return True
if not symbol_in_api(symbol.tags, arch, api):
return True
return False
@ -189,6 +201,15 @@ class ParseError(RuntimeError):
pass
class MultiplyDefinedSymbolError(RuntimeError):
"""A symbol name was multiply defined."""
def __init__(self, multiply_defined_symbols):
super(MultiplyDefinedSymbolError, self).__init__(
'Version script contains multiple definitions for: {}'.format(
', '.join(multiply_defined_symbols)))
self.multiply_defined_symbols = multiply_defined_symbols
class Version(object):
"""A version block of a symbol file."""
def __init__(self, name, base, tags, symbols):
@ -221,9 +242,12 @@ class Symbol(object):
class SymbolFileParser(object):
"""Parses NDK symbol files."""
def __init__(self, input_file, api_map):
def __init__(self, input_file, api_map, arch, api, vndk):
self.input_file = input_file
self.api_map = api_map
self.arch = arch
self.api = api
self.vndk = vndk
self.current_line = None
def parse(self):
@ -235,8 +259,36 @@ class SymbolFileParser(object):
else:
raise ParseError(
'Unexpected contents at top level: ' + self.current_line)
self.check_no_duplicate_symbols(versions)
return versions
def check_no_duplicate_symbols(self, versions):
"""Raises errors for multiply defined symbols.
This situation is the normal case when symbol versioning is actually
used, but this script doesn't currently handle that. The error message
will be a not necessarily obvious "error: redefition of 'foo'" from
stub.c, so it's better for us to catch this situation and raise a
better error.
"""
symbol_names = set()
multiply_defined_symbols = set()
for version in versions:
if should_omit_version(version, self.arch, self.api, self.vndk):
continue
for symbol in version.symbols:
if should_omit_symbol(symbol, self.arch, self.api, self.vndk):
continue
if symbol.name in symbol_names:
multiply_defined_symbols.add(symbol.name)
symbol_names.add(symbol.name)
if multiply_defined_symbols:
raise MultiplyDefinedSymbolError(
sorted(list(multiply_defined_symbols)))
def parse_version(self):
"""Parses a single version section and returns a Version object."""
name = self.current_line.split('{')[0].strip()
@ -325,20 +377,14 @@ class Generator(object):
def write_version(self, version):
"""Writes a single version block's data to the output files."""
name = version.name
tags = version.tags
if should_omit_version(name, tags, self.arch, self.api, self.vndk):
if should_omit_version(version, self.arch, self.api, self.vndk):
return
section_versioned = symbol_versioned_in_api(tags, self.api)
section_versioned = symbol_versioned_in_api(version.tags, self.api)
version_empty = True
pruned_symbols = []
for symbol in version.symbols:
if not self.vndk and 'vndk' in symbol.tags:
continue
if not symbol_in_arch(symbol.tags, self.arch):
continue
if not symbol_in_api(symbol.tags, self.arch, self.api):
if should_omit_symbol(symbol, self.arch, self.api, self.vndk):
continue
if symbol_versioned_in_api(symbol.tags, self.api):
@ -433,7 +479,11 @@ def main():
logging.basicConfig(level=verbose_map[verbosity])
with open(args.symbol_file) as symbol_file:
versions = SymbolFileParser(symbol_file, api_map).parse()
try:
versions = SymbolFileParser(symbol_file, api_map, args.arch, api,
args.vndk).parse()
except MultiplyDefinedSymbolError as ex:
sys.exit('{}: error: {}'.format(args.symbol_file, ex))
with open(args.stub_src, 'w') as src_file:
with open(args.version_script, 'w') as version_file:

View File

@ -163,39 +163,94 @@ class SymbolPresenceTest(unittest.TestCase):
class OmitVersionTest(unittest.TestCase):
def test_omit_private(self):
self.assertFalse(gsl.should_omit_version('foo', [], 'arm', 9, False))
self.assertTrue(gsl.should_omit_version(
'foo_PRIVATE', [], 'arm', 9, False))
self.assertTrue(gsl.should_omit_version(
'foo_PLATFORM', [], 'arm', 9, False))
self.assertTrue(gsl.should_omit_version(
'foo', ['platform-only'], 'arm', 9, False))
def test_omit_vndk(self):
self.assertTrue(gsl.should_omit_version(
'foo', ['vndk'], 'arm', 9, False))
self.assertFalse(gsl.should_omit_version('foo', [], 'arm', 9, True))
self.assertFalse(gsl.should_omit_version(
'foo', ['vndk'], 'arm', 9, True))
def test_omit_arch(self):
self.assertFalse(gsl.should_omit_version('foo', [], 'arm', 9, False))
self.assertFalse(gsl.should_omit_version(
'foo', ['arm'], 'arm', 9, False))
self.assertTrue(gsl.should_omit_version(
'foo', ['x86'], 'arm', 9, False))
def test_omit_api(self):
self.assertFalse(gsl.should_omit_version('foo', [], 'arm', 9, False))
self.assertFalse(
gsl.should_omit_version('foo', ['introduced=9'], 'arm', 9, False))
gsl.should_omit_version(
gsl.Version('foo', None, [], []), 'arm', 9, False))
self.assertTrue(
gsl.should_omit_version('foo', ['introduced=14'], 'arm', 9, False))
gsl.should_omit_version(
gsl.Version('foo_PRIVATE', None, [], []), 'arm', 9, False))
self.assertTrue(
gsl.should_omit_version(
gsl.Version('foo_PLATFORM', None, [], []), 'arm', 9, False))
self.assertTrue(
gsl.should_omit_version(
gsl.Version('foo', None, ['platform-only'], []), 'arm', 9,
False))
def test_omit_vndk(self):
self.assertTrue(
gsl.should_omit_version(
gsl.Version('foo', None, ['vndk'], []), 'arm', 9, False))
self.assertFalse(
gsl.should_omit_version(
gsl.Version('foo', None, [], []), 'arm', 9, True))
self.assertFalse(
gsl.should_omit_version(
gsl.Version('foo', None, ['vndk'], []), 'arm', 9, True))
def test_omit_arch(self):
self.assertFalse(
gsl.should_omit_version(
gsl.Version('foo', None, [], []), 'arm', 9, False))
self.assertFalse(
gsl.should_omit_version(
gsl.Version('foo', None, ['arm'], []), 'arm', 9, False))
self.assertTrue(
gsl.should_omit_version(
gsl.Version('foo', None, ['x86'], []), 'arm', 9, False))
def test_omit_api(self):
self.assertFalse(
gsl.should_omit_version(
gsl.Version('foo', None, [], []), 'arm', 9, False))
self.assertFalse(
gsl.should_omit_version(
gsl.Version('foo', None, ['introduced=9'], []), 'arm', 9,
False))
self.assertTrue(
gsl.should_omit_version(
gsl.Version('foo', None, ['introduced=14'], []), 'arm', 9,
False))
class OmitSymbolTest(unittest.TestCase):
def test_omit_vndk(self):
self.assertTrue(
gsl.should_omit_symbol(
gsl.Symbol('foo', ['vndk']), 'arm', 9, False))
self.assertFalse(
gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, True))
self.assertFalse(
gsl.should_omit_symbol(
gsl.Symbol('foo', ['vndk']), 'arm', 9, True))
def test_omit_arch(self):
self.assertFalse(
gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, False))
self.assertFalse(
gsl.should_omit_symbol(
gsl.Symbol('foo', ['arm']), 'arm', 9, False))
self.assertTrue(
gsl.should_omit_symbol(
gsl.Symbol('foo', ['x86']), 'arm', 9, False))
def test_omit_api(self):
self.assertFalse(
gsl.should_omit_symbol(gsl.Symbol('foo', []), 'arm', 9, False))
self.assertFalse(
gsl.should_omit_symbol(
gsl.Symbol('foo', ['introduced=9']), 'arm', 9, False))
self.assertTrue(
gsl.should_omit_symbol(
gsl.Symbol('foo', ['introduced=14']), 'arm', 9, False))
class SymbolFileParseTest(unittest.TestCase):
@ -207,7 +262,7 @@ class SymbolFileParseTest(unittest.TestCase):
# baz
qux
"""))
parser = gsl.SymbolFileParser(input_file, {})
parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
self.assertIsNone(parser.current_line)
self.assertEqual('foo', parser.next_line().strip())
@ -232,7 +287,7 @@ class SymbolFileParseTest(unittest.TestCase):
VERSION_2 {
} VERSION_1; # asdf
"""))
parser = gsl.SymbolFileParser(input_file, {})
parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.next_line()
version = parser.parse_version()
@ -256,7 +311,7 @@ class SymbolFileParseTest(unittest.TestCase):
input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 {
"""))
parser = gsl.SymbolFileParser(input_file, {})
parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.next_line()
with self.assertRaises(gsl.ParseError):
parser.parse_version()
@ -267,7 +322,7 @@ class SymbolFileParseTest(unittest.TestCase):
foo:
}
"""))
parser = gsl.SymbolFileParser(input_file, {})
parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.next_line()
with self.assertRaises(gsl.ParseError):
parser.parse_version()
@ -277,7 +332,7 @@ class SymbolFileParseTest(unittest.TestCase):
foo;
bar; # baz qux
"""))
parser = gsl.SymbolFileParser(input_file, {})
parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.next_line()
symbol = parser.parse_symbol()
@ -295,7 +350,7 @@ class SymbolFileParseTest(unittest.TestCase):
*;
};
"""))
parser = gsl.SymbolFileParser(input_file, {})
parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.next_line()
with self.assertRaises(gsl.ParseError):
parser.parse_version()
@ -307,7 +362,7 @@ class SymbolFileParseTest(unittest.TestCase):
*;
};
"""))
parser = gsl.SymbolFileParser(input_file, {})
parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.next_line()
version = parser.parse_version()
self.assertEqual([], version.symbols)
@ -318,7 +373,7 @@ class SymbolFileParseTest(unittest.TestCase):
foo
};
"""))
parser = gsl.SymbolFileParser(input_file, {})
parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.next_line()
with self.assertRaises(gsl.ParseError):
parser.parse_version()
@ -326,7 +381,7 @@ class SymbolFileParseTest(unittest.TestCase):
def test_parse_fails_invalid_input(self):
with self.assertRaises(gsl.ParseError):
input_file = io.StringIO('foo')
parser = gsl.SymbolFileParser(input_file, {})
parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
parser.parse()
def test_parse(self):
@ -347,7 +402,7 @@ class SymbolFileParseTest(unittest.TestCase):
qwerty;
} VERSION_1;
"""))
parser = gsl.SymbolFileParser(input_file, {})
parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
versions = parser.parse()
expected = [
@ -505,7 +560,7 @@ class IntegrationTest(unittest.TestCase):
wobble;
} VERSION_4;
"""))
parser = gsl.SymbolFileParser(input_file, api_map)
parser = gsl.SymbolFileParser(input_file, api_map, 'arm', 9, False)
versions = parser.parse()
src_file = io.StringIO()
@ -555,7 +610,7 @@ class IntegrationTest(unittest.TestCase):
*;
};
"""))
parser = gsl.SymbolFileParser(input_file, api_map)
parser = gsl.SymbolFileParser(input_file, api_map, 'arm', 9001, False)
versions = parser.parse()
src_file = io.StringIO()
@ -578,6 +633,38 @@ class IntegrationTest(unittest.TestCase):
""")
self.assertEqual(expected_version, version_file.getvalue())
def test_multiple_definition(self):
input_file = io.StringIO(textwrap.dedent("""\
VERSION_1 {
global:
foo;
foo;
bar;
baz;
qux; # arm
local:
*;
};
VERSION_2 {
global:
bar;
qux; # arm64
} VERSION_1;
VERSION_PRIVATE {
global:
baz;
} VERSION_2;
"""))
parser = gsl.SymbolFileParser(input_file, {}, 'arm', 16, False)
with self.assertRaises(gsl.MultiplyDefinedSymbolError) as cm:
parser.parse()
self.assertEquals(['bar', 'foo'],
cm.exception.multiply_defined_symbols)
def main():
suite = unittest.TestLoader().loadTestsFromName(__name__)