| # Copyright (C) 2011 Apple Inc. All rights reserved. | 
 | # | 
 | # Redistribution and use in source and binary forms, with or without | 
 | # modification, are permitted provided that the following conditions | 
 | # are met: | 
 | # 1. Redistributions of source code must retain the above copyright | 
 | #    notice, this list of conditions and the following disclaimer. | 
 | # 2. Redistributions in binary form must reproduce the above copyright | 
 | #    notice, this list of conditions and the following disclaimer in the | 
 | #    documentation and/or other materials provided with the distribution. | 
 | # | 
 | # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' | 
 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, | 
 | # THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | 
 | # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS | 
 | # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | 
 | # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | 
 | # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | 
 | # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | 
 | # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | 
 | # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF | 
 | # THE POSSIBILITY OF SUCH DAMAGE. | 
 |  | 
 | require "config" | 
 | require "ast" | 
 |  | 
 | OFFSET_HEADER_MAGIC_NUMBERS = [ 0x2e43fd66, 0x4379bfba ] | 
 | OFFSET_MAGIC_NUMBERS = [ 0x5c577ac7, 0x0ff5e755 ] | 
 |  | 
 | # | 
 | # MissingMagicValuesException | 
 | # | 
 | # Thrown when magic values are missing from the binary. | 
 | # | 
 |  | 
 | class MissingMagicValuesException < Exception | 
 | end | 
 |  | 
 | # | 
 | # offsetsList(ast) | 
 | # sizesList(ast) | 
 | # constsLists(ast) | 
 | # | 
 | # Returns a list of offsets, sizeofs, and consts used by the AST. | 
 | # | 
 |  | 
 | def offsetsList(ast) | 
 |     ast.filter(StructOffset).uniq.sort | 
 | end | 
 |  | 
 | def sizesList(ast) | 
 |     ast.filter(Sizeof).uniq.sort | 
 | end | 
 |  | 
 | def constsList(ast) | 
 |     ast.filter(ConstExpr).uniq.sort | 
 | end | 
 |  | 
 | def readInt(endianness, bytes) | 
 |     if endianness == :little | 
 |         # Little endian | 
 |         number = (bytes[0] << 0  | | 
 |                   bytes[1] << 8  | | 
 |                   bytes[2] << 16 | | 
 |                   bytes[3] << 24 | | 
 |                   bytes[4] << 32 | | 
 |                   bytes[5] << 40 | | 
 |                   bytes[6] << 48 | | 
 |                   bytes[7] << 56) | 
 |     else | 
 |         # Big endian | 
 |         number = (bytes[0] << 56 | | 
 |                   bytes[1] << 48 | | 
 |                   bytes[2] << 40 | | 
 |                   bytes[3] << 32 | | 
 |                   bytes[4] << 24 | | 
 |                   bytes[5] << 16 | | 
 |                   bytes[6] << 8  | | 
 |                   bytes[7] << 0) | 
 |     end | 
 |     if number > 0x7fffffff_ffffffff | 
 |         number -= 1 << 64 | 
 |     end | 
 |     number | 
 | end | 
 |  | 
 | def prepareMagic(endianness, numbers) | 
 |     magicBytes = [] | 
 |     numbers.each { | 
 |         | number | | 
 |         currentBytes = [] | 
 |         8.times { | 
 |             currentBytes << (number & 0xff) | 
 |             number >>= 8 | 
 |         } | 
 |         if endianness == :big | 
 |             currentBytes.reverse! | 
 |         end | 
 |         magicBytes += currentBytes | 
 |     } | 
 |     magicBytes | 
 | end | 
 |  | 
 | def fileBytes(file) | 
 |     fileBytes = [] | 
 |     File.open(file, "rb") { | 
 |         | inp | | 
 |         loop { | 
 |             byte = inp.getbyte | 
 |             break unless byte | 
 |             fileBytes << byte | 
 |         } | 
 |     } | 
 |     fileBytes | 
 | end | 
 |  | 
 | def sliceByteArrays(byteArray, pattern) | 
 |     result = [] | 
 |     lastSlicePoint = 0 | 
 |     (byteArray.length - pattern.length + 1).times { | 
 |         | index | | 
 |         foundOne = true | 
 |         pattern.length.times { | 
 |             | subIndex | | 
 |             if byteArray[index + subIndex] != pattern[subIndex] | 
 |                 foundOne = false | 
 |                 break | 
 |             end | 
 |         } | 
 |         if foundOne | 
 |             result << byteArray[lastSlicePoint...index] | 
 |             lastSlicePoint = index + pattern.length | 
 |         end | 
 |     } | 
 |  | 
 |     result << byteArray[lastSlicePoint...(byteArray.length)] | 
 |  | 
 |     result | 
 | end | 
 |  | 
 | # | 
 | # offsetsAndConfigurationIndex(file) -> | 
 | #     [[offsets, index], ...] | 
 | # | 
 | # Parses the offsets from a file and returns a list of offsets and the | 
 | # index of the configuration that is valid in this build target. | 
 | # | 
 |  | 
 | def offsetsAndConfigurationIndex(file) | 
 |     fileBytes = fileBytes(file) | 
 |     result = {} | 
 |  | 
 |     [:little, :big].each { | 
 |         | endianness | | 
 |         headerMagicBytes = prepareMagic(endianness, OFFSET_HEADER_MAGIC_NUMBERS) | 
 |         magicBytes = prepareMagic(endianness, OFFSET_MAGIC_NUMBERS) | 
 |  | 
 |         bigArray = sliceByteArrays(fileBytes, headerMagicBytes) | 
 |         unless bigArray.size <= 1 | 
 |             bigArray[1..-1].each { | 
 |                 | configArray | | 
 |                 array = sliceByteArrays(configArray, magicBytes) | 
 |                 index = readInt(endianness, array[1]) | 
 |                 offsets = [] | 
 |                 array[2..-1].each { | 
 |                     | data | | 
 |                     offsets << readInt(endianness, data) | 
 |                 } | 
 |                 result[index] = offsets | 
 |             } | 
 |         end | 
 |     } | 
 |  | 
 |     raise MissingMagicValuesException unless result.length >= 1 | 
 |  | 
 |     # result is {index1=>offsets1, index2=>offsets2} but we want to return | 
 |     # [[offsets1, index1], [offsets2, index2]]. | 
 |     return result.map { | 
 |         | pair | | 
 |         pair.reverse | 
 |     } | 
 | end | 
 |  | 
 | # | 
 | # offsetsAndConfigurationIndex(file) -> | 
 | #     [[offsets, index], ...] | 
 | # | 
 | # Parses the offsets from a file and all its variants and returns a list of | 
 | # offsets and the index of the configuration that is valid in this build target. | 
 | # | 
 |  | 
 | def offsetsAndConfigurationIndexForVariants(file, variants) | 
 |     results = [] | 
 |     variants.each { | 
 |         | current_variant | | 
 |         suffix = "" | 
 |         unless current_variant == "normal" | 
 |             suffix = "_" + current_variant | 
 |         end | 
 |         results << offsetsAndConfigurationIndex(file + suffix) | 
 |     } | 
 |     return results.flatten(1) | 
 | end | 
 |  | 
 | # | 
 | # configurationIndices(file) -> | 
 | #     [[offsets, index], ...] | 
 | # | 
 | # Parses the configurations from a file and returns a list of the indices of | 
 | # the configurations that are valid in this build target. | 
 | # | 
 |  | 
 | def configurationIndices(file) | 
 |     fileBytes = fileBytes(file) | 
 |     result = [] | 
 |  | 
 |     [:little, :big].each { | 
 |         | endianness | | 
 |         headerMagicBytes = prepareMagic(endianness, OFFSET_HEADER_MAGIC_NUMBERS) | 
 |  | 
 |         bigArray = sliceByteArrays(fileBytes, headerMagicBytes) | 
 |         unless bigArray.size <= 1 | 
 |             bigArray[1..-1].each { | 
 |                 | configArray | | 
 |                 result << readInt(endianness, configArray) | 
 |             } | 
 |         end | 
 |     } | 
 |  | 
 |     raise MissingMagicValuesException unless result.length >= 1 | 
 |  | 
 |     return result | 
 | end | 
 |  | 
 | # | 
 | # configurationIndicesForVariants(file, variants) -> | 
 | #     [[offsets, index], ...] | 
 | # | 
 | # Parses the configurations from a file and all its variants and returns a list | 
 | # of the indices of the configurations that are valid in this build target. | 
 | # | 
 |  | 
 | def configurationIndicesForVariants(file, variants) | 
 |     results = [] | 
 |     variants.each { | 
 |         | current_variant | | 
 |         suffix = "" | 
 |         unless current_variant == "normal" | 
 |             suffix = "_" + current_variant | 
 |         end | 
 |         results << configurationIndices(file + suffix) | 
 |     } | 
 |     return results.flatten(1) | 
 | end | 
 |  | 
 | # | 
 | # buildOffsetsMap(ast, extractedConstants) -> map | 
 | # | 
 | # Builds a mapping between StructOffset, Sizeof, and ConstExpr nodes and their values. | 
 | # | 
 |  | 
 | def buildOffsetsMap(ast, extractedConstants) | 
 |     map = {} | 
 |     astOffsetsList = offsetsList(ast) | 
 |     astSizesList = sizesList(ast) | 
 |     astConstsList = constsList(ast) | 
 |  | 
 |     raise unless astOffsetsList.size + astSizesList.size + astConstsList.size == extractedConstants.size | 
 |     astOffsetsList.each_with_index { | 
 |         | structOffset, index | | 
 |         map[structOffset] = extractedConstants.shift | 
 |     } | 
 |     astSizesList.each_with_index { | 
 |         | sizeof, index | | 
 |         map[sizeof] = extractedConstants.shift | 
 |     } | 
 |     astConstsList.each_with_index { | 
 |         | const, index | | 
 |         map[const] = extractedConstants.shift | 
 |     } | 
 |     map | 
 | end | 
 |  |