Esempio n. 1
0
def get_pch_include(file_path):
    print 'file_path', file_path
    ssk_path = os.path.join(git_repo_path, 'SignalServiceKit') + os.sep
    sm_path = os.path.join(git_repo_path, 'SignalMessaging') + os.sep
    s_path = os.path.join(git_repo_path, 'Signal') + os.sep
    sae_path = os.path.join(git_repo_path, 'SignalShareExtension') + os.sep
    print 'ssk_path', ssk_path
    print 'sm_path', sm_path
    print 's_path', s_path
    print 'sae_path', sae_path
    if file_path.startswith(ssk_path):
        return os.path.join(
            git_repo_path,
            "Pods/Target Support Files/SignalServiceKit/SignalServiceKit-prefix.pch"
        )
    elif file_path.startswith(sm_path):
        return os.path.join(git_repo_path,
                            "SignalMessaging/SignalMessaging-Prefix.pch")
    elif file_path.startswith(s_path):
        return os.path.join(git_repo_path, "Signal/Signal-Prefix.pch")
    elif file_path.startswith(sae_path):
        return os.path.join(
            git_repo_path,
            "SignalShareExtension/SignalShareExtension-Prefix.pch")
    else:
        fail("Couldn't determine .pch for file:", file_path)
Esempio n. 2
0
def process_objc_property_impl(clazz, prefix, file_path, line, remainder):
    # print '\t\t', 'process_objc_property_impl', remainder

    match = process_objc_property_impl_regex.search(remainder)
    if match is None:
        print 'file_path:', file_path
        fail('Could not match line:', line)
    property_name = match.group(1).strip()
    property = clazz.get_property(property_name)
    if property is None:
        if clazz.name == 'AppDelegate' and property_name == 'window':
            # We can't parse properties synthesized locally but
            # declared in a protocol defined in the iOS frameworks.
            # So, special case these propert(y/ies) - we don't need
            # to handle them.
            return

        print 'file_path:', file_path
        print 'line:', line
        print '\t', 'clazz', clazz.name, clazz.counter
        print '\t', 'property_name', property_name
        for name in clazz.property_names():
            print '\t\t', name
        fail("Can't find property:", property_name)
    else:
        print 'property synthesized', line
        property.is_synthesized = True
Esempio n. 3
0
def process_objc_type_declaration(namespace, file_path, lines, prefix, remainder):
    print '\t', 'type_declaration', remainder

    match = process_objc_type_declaration_regex.search(remainder)
    if match is None:
        print 'file_path:', file_path
        fail('Could not match line:', remainder)
    type1 = get_match_group(match, 1)
    type2 = get_match_group(match, 2)
    type3 = get_match_group(match, 3)

    if type1 is None or type3 is None:
        return
    is_enum = (type1 == 'enum ' + type3)
    if not is_enum:
        return

    # print 'type_declaration:', type1, type2, type3

    if type3.startswith('line:'):
        print 'Ignoring invalid enum(2):', type1, type2, type3
        return
    if type3 not in enum_type_map:
        print 'Enum has unknown type:', type3
        enum_type = 'NSUInteger'
    else:
        enum_type = enum_type_map[type3]
    enum_type_map[type3] = enum_type
Esempio n. 4
0
def process_objc_category(namespace, file_path, lines, decl_prefix,
                          decl_remainder):
    # print '\t', 'category'

    # |-ObjCCategoryDecl 0x1092f8440 <line:76:1, line:81:2> line:76:12 SomeCategory
    # | |-ObjCInterface 0x1092f5d58 'ObjCMessageWAuthor'
    # | |-ObjCCategoryImpl 0x1092f8608 'SomeCategory'
    # | |-ObjCPropertyDecl 0x1092f8508 <line:79:1, col:53> col:53 fakeProperty2 'NSString * _Nullable':'NSString *' readonly nonatomic
    # | `-ObjCMethodDecl 0x1092f8580 <col:53> col:53 implicit - fakeProperty2 'NSString * _Nullable':'NSString *'
    if not lines.hasNext():
        fail('Category missing interface.')
    line = lines.next()
    prefix, remainder = split_objc_ast_prefix(line)
    if len(prefix) <= len(decl_prefix):
        fail('Category missing interface.')
    class_name = remainder.split(' ')[-1]
    if class_name.startswith("'") and class_name.endswith("'"):
        class_name = class_name[1:-1]

    process_objc_class(namespace,
                       file_path,
                       lines,
                       decl_prefix,
                       decl_remainder,
                       custom_class_name=class_name)
Esempio n. 5
0
    def class_protocols(self):
        result = []
        for protocol_name in self.protocol_names:
            if protocol_name == self.name:
                # There are classes that implement a protocol of the same name, e.g. MTLModel
                # Ignore them.
                continue

            # print 'get_inherited_property:', name, self.name, protocol_name

            protocol = self.namespace.find_class(protocol_name)
            if protocol is None:
                if protocol_name.startswith('NS') or protocol_name.startswith(
                        'AV') or protocol_name.startswith(
                            'UI') or protocol_name.startswith(
                                'MF') or protocol_name.startswith(
                                    'UN') or protocol_name.startswith('CN'):
                    # Ignore built in protocols.
                    continue
                print 'clazz:', self.name
                print 'file_path:', file_path
                fail('Missing protocol:', protocol_name)

            result.append(protocol)

        return result
Esempio n. 6
0
def split_objc_ast_prefix(line):
    match = split_objc_ast_prefix_regex.search(line)
    if match is None:
        fail('Could not match line:', line)
    prefix = match.group(1)
    remainder = match.group(2)
    # print 'prefix', prefix
    # print 'remainder', remainder
    return prefix, remainder
Esempio n. 7
0
def process_objc_class(namespace, file_path, lines, decl_prefix, decl_remainder, custom_class_name=None, super_class_name=None):
    if custom_class_name is not None:
        class_name = custom_class_name
    else:
        class_name = decl_remainder.split(' ')[-1]
    # print '\t', 'class_name', class_name

    clazz = namespace.upsert_class(class_name)

    if super_class_name is not None:
        if clazz.super_class_name is None:
            clazz.super_class_name = super_class_name
        elif clazz.super_class_name != super_class_name:
            fail("super_class_name does not match:", clazz.super_class_name, super_class_name)

    while lines.hasNext():
        line = lines.next()
        prefix, remainder = split_objc_ast_prefix(line)
        if len(prefix) <= len(decl_prefix):
            # Declaration is over.
            return clazz

        line = lines.popNext()
        # print '\t', 'interface', 'line', line


        # print '\t', '\t', 'remainder', remainder

        # | |-ObjCPropertyDecl 0x10f5d2d40 <line:19:1, col:43> col:43 uniqueId 'NSString * _Nonnull':'NSString *' readonly nonatomic

        """
        TODO: We face interesting choices about how to process:

        * properties
        * private properties
        * properties with renamed ivars
        * ivars without properties
        * properties not backed by ivars (e.g. actually accessors).
        """
        # print 'remainder', remainder
        if remainder.startswith('ObjCPropertyDecl '):
            # print 'property ?', line
            process_objc_property(clazz, prefix, file_path, line, remainder)
        elif remainder.startswith('ObjCPropertyImplDecl '):
            # print 'property impl', line
            process_objc_property_impl(clazz, prefix, file_path, line, remainder)
        elif remainder.startswith('ObjCMethodDecl '):
            # print 'property impl', line
            process_objc_method_decl(clazz, prefix, file_path, line, remainder)
        elif remainder.startswith('ObjCProtocol '):
            # print 'property impl', line
            process_objc_protocol(namespace, clazz, prefix, file_path, line, remainder)

    return clazz
Esempio n. 8
0
def process_objc_ast(namespace: Namespace, file_path: str,
                     raw_ast: str) -> None:
    m_filename = os.path.basename(file_path)
    print('m_filename:', m_filename)
    file_base, file_extension = os.path.splitext(m_filename)
    if file_extension != '.m':
        fail('Bad file extension:', file_extension)
    h_filename = file_base + '.h'
    print('h_filename:', h_filename)

    # TODO: Remove
    lines = raw_ast.split('\n')
    # lines = lines[-1000:]
    raw_ast = '\n'.join(lines)

    lines = LineProcessor(raw_ast)
    while lines.hasNext():
        line = lines.popNext()
        # print 'line:', line
        prefix, remainder = split_objc_ast_prefix(line)

        if remainder.startswith('ObjCInterfaceDecl '):
            # |-ObjCInterfaceDecl 0x112510490 <SignalDataStoreCommon/ObjCMessage.h:14:1, line:25:2> line:14:12 ObjCMessage
            # print 'interface', line
            process_objc_interface(namespace, file_path, lines, prefix,
                                   remainder)
        elif remainder.startswith('ObjCCategoryDecl '):
            # |-ObjCCategoryDecl 0x112510d58 <SignalDataStoreCommon/ObjCMessage.m:18:1, line:22:2> line:18:12
            # print 'category', line
            process_objc_category(namespace, file_path, lines, prefix,
                                  remainder)
        elif remainder.startswith('ObjCImplementationDecl '):
            # `-ObjCImplementationDecl 0x112510f20 <line:24:1, line:87:1> line:24:17 ObjCMessage
            # print 'implementation', line
            process_objc_implementation(namespace, file_path, lines, prefix,
                                        remainder)
        elif remainder.startswith('ObjCProtocolDecl '):
            # `-ObjCImplementationDecl 0x112510f20 <line:24:1, line:87:1> line:24:17 ObjCMessage
            # print 'implementation', line
            process_objc_protocol_decl(namespace, file_path, lines, prefix,
                                       remainder)
        # TODO: Category impl.
        elif remainder.startswith('TypedefDecl '):
            # `-ObjCImplementationDecl 0x112510f20 <line:24:1, line:87:1> line:24:17 ObjCMessage
            # print 'implementation', line
            process_objc_type_declaration(namespace, file_path, lines, prefix,
                                          remainder)
        elif remainder.startswith('EnumDecl '):
            # `-ObjCImplementationDecl 0x112510f20 <line:24:1, line:87:1> line:24:17 ObjCMessage
            # print 'implementation', line
            process_objc_enum_declaration(namespace, file_path, lines, prefix,
                                          remainder)
Esempio n. 9
0
def process_file(file_path, namespace, intermediates):
    filename = os.path.basename(file_path)
    if not filename.endswith('.swift'):
        return
    if filename == 'EmojiWithSkinTones+String.swift':
        return

    command = [
        'which',
        'sourcekitten',
        ]
    exit_code, output, error_output = ows_getoutput(command)
    if exit_code != 0:
        fail('Missing sourcekitten. Install with homebrew?')

    print 'Extracting Swift Bridging Info For:', file_path
    command = [
        'sourcekitten',
        'structure',
        '--file',
        file_path,
        ]
    # for part in command:
    #     print '\t', part
    # command = ' '.join(command).strip()
    # print 'command', command
    # output = commands.getoutput(command)

    # command = ' '.join(command).strip()
    # print 'command', command
    exit_code, output, error_output = ows_getoutput(command)
    if exit_code != 0:
        print 'exit_code:', exit_code
    if len(error_output.strip()) > 0:
        print 'error_output:', error_output
    # print 'output:', len(output)

    # exit(1)

    output = output.strip()
    # print 'output', output


    if intermediates:
        intermediate_file_path = file_path + '.ast'
        print 'Writing intermediate:', intermediate_file_path
        with open(intermediate_file_path, 'wt') as f:
            f.write(output)


    parse_swift_ast(file_path, namespace, output)
Esempio n. 10
0
def parse_swift_ast(file_path, namespace, ast):
    json_data = json.loads(ast)

    json_maps = json_data.get('key.substructure')
    if json_maps is None:
        return

    for json_map in json_maps:
      kind = json_map.get('key.kind')
      if kind is None:
          continue
      elif kind == 'source.lang.swift.decl.protocol':
          # "key.kind" : "source.lang.swift.decl.protocol",
          # "key.length" : 1067,
          # "key.name" : "TypingIndicators",
          # "key.namelength" : 16,
          # "key.nameoffset" : 135,
          # "key.offset" : 126,
          # "key.runtime_name" : "OWSTypingIndicators",
          name = json_map.get('key.runtime_name')
          if name is None or len(name) < 1 or name.startswith('_'):
              name = json_map.get('key.name')
          if name is None or len(name) < 1:
              fail('protocol is missing name.')
              continue
          if name.startswith('_'):
              continue
          namespace.swift_protocol_names.append(name)
      elif kind == 'source.lang.swift.decl.class':
          # "key.kind" : "source.lang.swift.decl.class",
          # "key.length" : 15057,
          # "key.name" : "TypingIndicatorsImpl",
          # "key.namelength" : 20,
          # "key.nameoffset" : 1251,
          # "key.offset" : 1245,
          # "key.runtime_name" : "OWSTypingIndicatorsImpl",
          name = json_map.get('key.runtime_name')
          if name is None or len(name) < 1 or name.startswith('_'):
              name = json_map.get('key.name')
          if name is None or len(name) < 1:
              fail('class is missing name.')
              continue
          if name.startswith('_'):
              continue
          namespace.swift_class_names.append(name)
Esempio n. 11
0
def process_objc(file_path, swift_bridging_path, intermediates):
    module_header_dir_path = gather_module_headers('Pods')
    pch_include = get_pch_include(file_path)

    # These clang args can be found by building our workspace and looking at how XCode invokes clang.
    clang_args = '-arch arm64 -fmessage-length=0 -fdiagnostics-show-note-include-stack -fmacro-backtrace-limit=0 -std=gnu11 -fobjc-arc -fobjc-weak -fmodules -gmodules -fmodules-prune-interval=86400 -fmodules-prune-after=345600 -Wnon-modular-include-in-framework-module -Werror=non-modular-include-in-framework-module -fapplication-extension -Wno-trigraphs -fpascal-strings -O0 -fno-common -Wno-missing-field-initializers -Wno-missing-prototypes -Werror=return-type -Wdocumentation -Wunreachable-code -Wno-implicit-atomic-properties -Werror=deprecated-objc-isa-usage -Wno-objc-interface-ivars -Werror=objc-root-class -Wno-arc-repeated-use-of-weak -Wimplicit-retain-self -Wduplicate-method-match -Wno-missing-braces -Wparentheses -Wswitch -Wunused-function -Wno-unused-label -Wno-unused-parameter -Wunused-variable -Wunused-value -Wempty-body -Wuninitialized -Wconditional-uninitialized -Wno-unknown-pragmas -Wno-shadow -Wno-four-char-constants -Wno-conversion -Wconstant-conversion -Wint-conversion -Wbool-conversion -Wenum-conversion -Wno-float-conversion -Wnon-literal-null-conversion -Wobjc-literal-conversion -Wshorten-64-to-32 -Wpointer-sign -Wno-newline-eof -Wno-selector -Wno-strict-selector-match -Wundeclared-selector -Wdeprecated-implementations'.split(' ')

    command = [
        'xcrun',
        '--show-sdk-path',
        '--sdk',
        'iphoneos',
    ]
    exit_code, output, error_output = ows_getoutput(command)
    if int(exit_code) != 0:
        fail('Could not find iOS SDK.')
    iphoneos_sdk_path = output.strip()

    # TODO: We'll never repro the correct search paths, so clang will always emit errors.
    #       We'll want to ignore these errors without silently failing.
    command = [
        'clang',
        '-x',
        'objective-c',
        '-Xclang',
        '-ast-dump',
        '-fobjc-arc',
        ] + clang_args + [
        '-isysroot',
        iphoneos_sdk_path,
        ] + find_header_include_paths('SignalServiceKit/src') + [
        ] + find_header_include_paths('SignalMessaging') + [
        # ] + find_header_include_paths('Pods') + [
        ('-I' + module_header_dir_path),
        ('-I' + swift_bridging_path),
        '-I/Users/matthew/code/workspace/ows/Signal-iOS-2/Pods/SignalCoreKit/SignalCoreKit',
        '-include',
        pch_include,
        file_path,
        # '|',
        # 'sed',
        # "$'s,\x1b\\[[0-9;]*[a-zA-Z],,g'",
        # '>',
        # 'test4.txt'
    ]
    for part in command:
        print '\t', part
    # command = ' '.join(command).strip()
    # print 'command', command
    # output = commands.getoutput(command)

    # command = ' '.join(command).strip()
    print 'command', command
    exit_code, output, error_output = ows_getoutput(command)
    print 'exit_code:', exit_code
    print 'error_output:', error_output
    # print 'output:', len(output)

    # exit(1)

    output = output.strip()
    raw_ast = output

    if intermediates:
        intermediate_file_path = file_path + '.ast'
        print 'Writing intermediate:', intermediate_file_path
        with open(intermediate_file_path, 'wt') as f:
            f.write(raw_ast)
    print 'raw_ast:', len(raw_ast)

    namespace = Namespace()

    process_objc_ast(namespace, file_path, raw_ast)

    output = emit_output(file_path, namespace)

    print 'output', output

    parsed_file_path = file_path + sds_common.SDS_JSON_FILE_EXTENSION
    print 'parsed_file_path', parsed_file_path
    with open(parsed_file_path, 'wt') as f:
        f.write(output)
Esempio n. 12
0
def process_objc_property(clazz, prefix, file_path, line, remainder):

    match = process_objc_property_regex.search(remainder)
    if match is None:
        print 'file_path:', file_path
        print 'remainder:', remainder
        fail('Could not match line:', line)
    property_name = match.group(1).strip()
    property_type_1 = get_match_group(match, 2)
    property_type_2 = get_match_group(match, 4)
    property_keywords = match.group(5).strip().split(' ')

    is_optional = (property_type_2 + ' _Nullable') == property_type_1
    is_readonly = 'readonly' in property_keywords

    property_type = property_type_2
    if len(property_type_2) < 1:
        property_type = property_type_1

    primitive_types = (
        'BOOL',
        'NSInteger',
        'NSUInteger',
        'uint64_t',
        'int64_t'
    )
    if property_type_1 in primitive_types:
        property_type = property_type_1
    # property_type = property_type_1

    print '\t', property_name, 'property_type', property_type, 'property_type1', property_type_1, 'property_type2', property_type_2, 'property_type', property_type

    property = clazz.get_property(property_name)
    if property is None:

        property = ParsedProperty(property_name, property_type, is_optional)
        # print 'property found', line
        clazz.add_property(property)
    else:
        if property.name != property_name:
            fail("Property names don't match", property.name, property_name)
        if property.is_optional != is_optional:
            if clazz.name.startswith('DD'):
                # CocoaLumberjack has nullability consistency issues.
                # Ignore them.
                return
            print 'file_path:', file_path
            print 'clazz:', clazz.name
            fail("Property is_optional don't match", property_name)
        if property.objc_type != property_type:
            # There's a common pattern of using a mutable private property
            # and exposing a non-mutable public property to prevent
            # external modification of the property.
            if property_type.startswith('NSMutable') and property.objc_type == 'NS' + property_type[len('NSMutable'):]:
                property.objc_type = property_type
            else:
                print 'file_path:', file_path
                print 'remainder:', remainder
                print 'property.objc_type:', property.objc_type
                print 'property_type:', property_type
                print 'property_name:', property_name
                fail("Property types don't match", property.objc_type, property_type)


    if not is_readonly:
        property.is_not_readonly = True