@futpib/parser 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +149 -0
- package/.github/workflows/copilot-setup-steps.yml +18 -0
- package/.github/workflows/main.yml +29 -8
- package/.yarn/releases/yarn-4.9.4.cjs +942 -0
- package/.yarnrc.yml +1 -1
- package/build/allSettledStream.js +32 -14
- package/build/allSettledStream.test.js +32 -0
- package/build/androidPackage.d.ts +39 -0
- package/build/androidPackageParser.d.ts +17 -0
- package/build/androidPackageParser.js +185 -0
- package/build/androidPackageParser.test.js +22 -0
- package/build/androidPackageUnparser.d.ts +4 -0
- package/build/androidPackageUnparser.js +94 -0
- package/build/androidPackageUnparser.test.js +26 -0
- package/build/arbitrarilySlicedAsyncInterable.d.ts +3 -1
- package/build/arbitrarilySlicedAsyncInterable.js +3 -3
- package/build/arbitrarilySlicedAsyncInterator.js +2 -1
- package/build/arbitraryDalvikBytecode.d.ts +4 -0
- package/build/arbitraryDalvikBytecode.js +640 -0
- package/build/arbitraryDalvikExecutable.d.ts +3 -0
- package/build/arbitraryDalvikExecutable.js +282 -0
- package/build/arbitraryDosDateTime.js +1 -0
- package/build/arbitraryZipStream.js +1 -1
- package/build/arrayParser.js +2 -2
- package/build/arrayParser.test.js +3 -3
- package/build/arrayUnparser.d.ts +1 -1
- package/build/backsmali.d.ts +3 -0
- package/build/backsmali.js +50 -0
- package/build/bsonParser.js +6 -2
- package/build/customInvariant.d.ts +2 -1
- package/build/customInvariant.js +4 -6
- package/build/dalvikBytecodeParser/formatParsers.d.ts +171 -0
- package/build/dalvikBytecodeParser/formatParsers.js +304 -0
- package/build/dalvikBytecodeParser/formatSizes.d.ts +34 -0
- package/build/dalvikBytecodeParser/formatSizes.js +34 -0
- package/build/dalvikBytecodeParser/operationFormats.d.ts +225 -0
- package/build/dalvikBytecodeParser/operationFormats.js +225 -0
- package/build/dalvikBytecodeParser.d.ts +1207 -0
- package/build/dalvikBytecodeParser.js +1289 -0
- package/build/dalvikBytecodeUnparser/formatUnparsers.d.ts +152 -0
- package/build/dalvikBytecodeUnparser/formatUnparsers.js +225 -0
- package/build/dalvikBytecodeUnparser.d.ts +3 -0
- package/build/dalvikBytecodeUnparser.js +642 -0
- package/build/dalvikBytecodeUnparser.test.js +25 -0
- package/build/dalvikExecutable.d.ts +215 -0
- package/build/dalvikExecutable.js +56 -0
- package/build/dalvikExecutableParser/stringSyntaxParser.d.ts +4 -0
- package/build/dalvikExecutableParser/stringSyntaxParser.js +76 -0
- package/build/dalvikExecutableParser/typeParsers.d.ts +11 -0
- package/build/dalvikExecutableParser/typeParsers.js +39 -0
- package/build/dalvikExecutableParser/typedNumbers.d.ts +106 -0
- package/build/dalvikExecutableParser/typedNumbers.js +18 -0
- package/build/dalvikExecutableParser.d.ts +5 -0
- package/build/dalvikExecutableParser.js +1757 -0
- package/build/dalvikExecutableParser.test.js +72 -0
- package/build/dalvikExecutableParserAgainstSmaliParser.test.js +275 -0
- package/build/dalvikExecutableUnparser/annotationUnparsers.d.ts +14 -0
- package/build/dalvikExecutableUnparser/annotationUnparsers.js +97 -0
- package/build/dalvikExecutableUnparser/poolBuilders.d.ts +49 -0
- package/build/dalvikExecutableUnparser/poolBuilders.js +140 -0
- package/build/dalvikExecutableUnparser/poolScanners.d.ts +4 -0
- package/build/dalvikExecutableUnparser/poolScanners.js +220 -0
- package/build/dalvikExecutableUnparser/sectionUnparsers.d.ts +25 -0
- package/build/dalvikExecutableUnparser/sectionUnparsers.js +581 -0
- package/build/dalvikExecutableUnparser/utils.d.ts +10 -0
- package/build/dalvikExecutableUnparser/utils.js +108 -0
- package/build/dalvikExecutableUnparser.d.ts +4 -0
- package/build/dalvikExecutableUnparser.js +406 -0
- package/build/dalvikExecutableUnparser.test.js +31 -0
- package/build/debugLogInputParser.d.ts +4 -0
- package/build/debugLogInputParser.js +16 -0
- package/build/debugLogParser.js +14 -3
- package/build/disjunctionParser.d.ts +2 -1
- package/build/disjunctionParser.js +6 -4
- package/build/elementTerminatedArrayParser.d.ts +3 -0
- package/build/elementTerminatedArrayParser.js +18 -0
- package/build/elementTerminatedArrayParser.test.js +52 -0
- package/build/elementTerminatedSequenceArrayParser.d.ts +3 -0
- package/build/elementTerminatedSequenceArrayParser.js +32 -0
- package/build/elementTerminatedSequenceArrayParser.test.js +34 -0
- package/build/elementTerminatedSequenceParser.d.ts +3 -0
- package/build/elementTerminatedSequenceParser.js +27 -0
- package/build/elementTerminatedSequenceParser.test.js +34 -0
- package/build/endOfInputParser.d.ts +1 -1
- package/build/exactElementParser.js +10 -5
- package/build/exactElementSwitchParser.d.ts +3 -0
- package/build/exactElementSwitchParser.js +22 -0
- package/build/exactSequenceParser.d.ts +2 -1
- package/build/exactSequenceParser.js +12 -2
- package/build/fetchCid.d.ts +1 -0
- package/build/fetchCid.js +103 -0
- package/build/fetchCid.test.js +16 -0
- package/build/fixedLengthSequenceParser.d.ts +1 -0
- package/build/fixedLengthSequenceParser.js +18 -1
- package/build/fixedLengthSequenceParser.test.js +41 -0
- package/build/hasExecutable.d.ts +1 -0
- package/build/hasExecutable.js +8 -0
- package/build/highResolutionTimer.d.ts +16 -0
- package/build/highResolutionTimer.js +42 -0
- package/build/inputReader.d.ts +11 -0
- package/build/inputReader.js +37 -0
- package/build/inputReader.test.js +161 -8
- package/build/inputReaderState.d.ts +10 -0
- package/build/inputReaderState.js +16 -0
- package/build/inspect.d.ts +1 -0
- package/build/inspect.js +7 -0
- package/build/javaKeyStoreParser.test.js +8 -8
- package/build/jsonParser.d.ts +2 -0
- package/build/jsonParser.js +19 -22
- package/build/lazyMessageError.d.ts +48 -0
- package/build/lazyMessageError.js +53 -0
- package/build/lazyMessageError.test.d.ts +1 -0
- package/build/lazyMessageError.test.js +15 -0
- package/build/leb128Parser.d.ts +7 -0
- package/build/leb128Parser.js +82 -0
- package/build/leb128Parser.test.d.ts +1 -0
- package/build/leb128Parser.test.js +107 -0
- package/build/lookaheadParser.d.ts +2 -0
- package/build/lookaheadParser.js +14 -0
- package/build/negativeLookaheadParser.js +22 -16
- package/build/negativeLookaheadParser.test.d.ts +1 -0
- package/build/negativeLookaheadParser.test.js +30 -0
- package/build/noStackCaptureOverheadError.d.ts +4 -0
- package/build/noStackCaptureOverheadError.js +9 -0
- package/build/noStackCaptureOverheadError.test.d.ts +1 -0
- package/build/noStackCaptureOverheadError.test.js +15 -0
- package/build/nonEmptyArrayParser.d.ts +2 -0
- package/build/nonEmptyArrayParser.js +32 -0
- package/build/nonEmptyArrayParser.test.d.ts +1 -0
- package/build/nonEmptyArrayParser.test.js +17 -0
- package/build/optionalParser.js +2 -2
- package/build/parser.d.ts +11 -1
- package/build/parser.js +82 -32
- package/build/parser.test.js +141 -25
- package/build/parserAccessorParser.js +9 -1
- package/build/parserConsumedSequenceParser.d.ts +1 -1
- package/build/parserConsumedSequenceParser.js +21 -16
- package/build/parserContext.d.ts +22 -6
- package/build/parserContext.js +113 -57
- package/build/parserContext.test.js +33 -2
- package/build/parserCreatorCompose.d.ts +1 -0
- package/build/parserCreatorCompose.js +8 -2
- package/build/parserError.d.ts +605 -40
- package/build/parserError.js +98 -53
- package/build/parserImplementationInvariant.d.ts +1 -1
- package/build/parserImplementationInvariant.js +2 -2
- package/build/parserInputCompanion.d.ts +15 -0
- package/build/parserInputCompanion.js +38 -0
- package/build/promiseCompose.d.ts +1 -1
- package/build/promiseCompose.js +11 -1
- package/build/promiseSettled.d.ts +1 -0
- package/build/promiseSettled.js +4 -0
- package/build/separatedArrayParser.d.ts +2 -0
- package/build/separatedArrayParser.js +39 -0
- package/build/separatedArrayParser.test.d.ts +1 -0
- package/build/separatedArrayParser.test.js +21 -0
- package/build/separatedNonEmptyArrayParser.d.ts +2 -0
- package/build/separatedNonEmptyArrayParser.js +40 -0
- package/build/separatedNonEmptyArrayParser.test.d.ts +1 -0
- package/build/separatedNonEmptyArrayParser.test.js +66 -0
- package/build/sequenceBuffer.d.ts +10 -0
- package/build/sequenceBuffer.js +54 -2
- package/build/sequenceBuffer.test.js +57 -0
- package/build/sequenceTerminatedSequenceParser.d.ts +5 -0
- package/build/sequenceTerminatedSequenceParser.js +32 -0
- package/build/sequenceTerminatedSequenceParser.test.d.ts +1 -0
- package/build/sequenceTerminatedSequenceParser.test.js +37 -0
- package/build/sequenceUnparser.d.ts +1 -1
- package/build/skipParser.d.ts +1 -1
- package/build/skipParser.js +4 -2
- package/build/skipToParser.d.ts +2 -0
- package/build/skipToParser.js +11 -0
- package/build/sliceBoundedParser.d.ts +1 -1
- package/build/sliceBoundedParser.js +7 -2
- package/build/sliceBoundedParser.test.js +30 -1
- package/build/smali.d.ts +1 -0
- package/build/smali.js +21 -0
- package/build/smaliParser.d.ts +68 -0
- package/build/smaliParser.js +2081 -0
- package/build/smaliParser.test.d.ts +1 -0
- package/build/smaliParser.test.js +410 -0
- package/build/stringFromAsyncIterable.d.ts +1 -0
- package/build/stringFromAsyncIterable.js +7 -0
- package/build/terminatedArrayParser.d.ts +3 -1
- package/build/terminatedArrayParser.js +79 -2
- package/build/terminatedArrayParser.test.d.ts +1 -0
- package/build/terminatedArrayParser.test.js +131 -0
- package/build/toAsyncIterable.d.ts +1 -0
- package/build/toAsyncIterable.js +7 -0
- package/build/toAsyncIterator.d.ts +1 -0
- package/build/toAsyncIterator.js +33 -0
- package/build/tupleParser.d.ts +4 -0
- package/build/tupleParser.js +1 -5
- package/build/unionParser.d.ts +2 -1
- package/build/unionParser.js +29 -14
- package/build/unionParser.test.d.ts +1 -0
- package/build/unionParser.test.js +60 -0
- package/build/unparser.d.ts +3 -3
- package/build/unparser.js +6 -4
- package/build/unparser.test.js +7 -19
- package/build/unparserContext.d.ts +2 -2
- package/build/unparserContext.js +2 -3
- package/build/unparserError.d.ts +2 -1
- package/build/unparserError.js +2 -1
- package/build/unparserImplementationInvariant.d.ts +1 -1
- package/build/unparserOutputCompanion.d.ts +1 -1
- package/build/unparserOutputCompanion.js +1 -1
- package/build/zipParser.d.ts +7 -2
- package/build/zipParser.js +36 -12
- package/build/zipUnparser.d.ts +7 -4
- package/build/zipUnparser.js +64 -45
- package/build/zipUnparser.test.js +15 -15
- package/package.json +33 -24
- package/src/allSettledStream.test.ts +40 -0
- package/src/allSettledStream.ts +47 -15
- package/src/androidPackage.ts +48 -0
- package/src/androidPackageParser.test.ts +27 -0
- package/src/{apkParser.test.ts.md → androidPackageParser.test.ts.md} +4 -4
- package/src/androidPackageParser.test.ts.snap +0 -0
- package/src/androidPackageParser.ts +398 -0
- package/src/androidPackageUnparser.test.ts +34 -0
- package/src/androidPackageUnparser.ts +126 -0
- package/src/arbitrarilySlicedAsyncInterable.ts +7 -2
- package/src/arbitrarilySlicedAsyncInterator.ts +4 -4
- package/src/arbitraryDalvikBytecode.ts +992 -0
- package/src/arbitraryDalvikExecutable.ts +434 -0
- package/src/arbitraryDosDateTime.ts +1 -0
- package/src/arbitraryZipStream.ts +1 -1
- package/src/arrayParser.test.ts +3 -3
- package/src/arrayParser.ts +2 -2
- package/src/arrayUnparser.ts +2 -2
- package/src/backsmali.ts +74 -0
- package/src/bsonParser.test.ts +12 -14
- package/src/bsonParser.ts +13 -2
- package/src/customInvariant.ts +8 -12
- package/src/dalvikBytecodeParser/formatParsers.ts +780 -0
- package/src/dalvikBytecodeParser/formatSizes.ts +35 -0
- package/src/dalvikBytecodeParser/operationFormats.ts +226 -0
- package/src/dalvikBytecodeParser.ts +2873 -0
- package/src/dalvikBytecodeUnparser/formatUnparsers.ts +442 -0
- package/src/dalvikBytecodeUnparser.test.ts +44 -0
- package/src/dalvikBytecodeUnparser.ts +758 -0
- package/src/dalvikExecutable.ts +282 -0
- package/src/dalvikExecutableParser/stringSyntaxParser.ts +145 -0
- package/src/dalvikExecutableParser/typeParsers.ts +74 -0
- package/src/dalvikExecutableParser/typedNumbers.ts +57 -0
- package/src/dalvikExecutableParser.test.ts +89 -0
- package/src/dalvikExecutableParser.test.ts.md +634 -0
- package/src/dalvikExecutableParser.test.ts.snap +0 -0
- package/src/dalvikExecutableParser.ts +3245 -0
- package/src/dalvikExecutableParserAgainstSmaliParser.test.ts +363 -0
- package/src/dalvikExecutableUnparser/annotationUnparsers.ts +135 -0
- package/src/dalvikExecutableUnparser/poolBuilders.ts +189 -0
- package/src/dalvikExecutableUnparser/poolScanners.ts +297 -0
- package/src/dalvikExecutableUnparser/sectionUnparsers.ts +683 -0
- package/src/dalvikExecutableUnparser/utils.ts +149 -0
- package/src/dalvikExecutableUnparser.test.ts +57 -0
- package/src/dalvikExecutableUnparser.ts +581 -0
- package/src/debugLogInputParser.ts +28 -0
- package/src/debugLogParser.ts +19 -3
- package/src/disjunctionParser.ts +12 -7
- package/src/elementTerminatedArrayParser.test.ts +99 -0
- package/src/elementTerminatedArrayParser.ts +31 -0
- package/src/elementTerminatedSequenceArrayParser.test.ts +52 -0
- package/src/elementTerminatedSequenceArrayParser.ts +52 -0
- package/src/elementTerminatedSequenceParser.test.ts +52 -0
- package/src/elementTerminatedSequenceParser.ts +43 -0
- package/src/endOfInputParser.ts +1 -1
- package/src/exactElementParser.ts +17 -11
- package/src/exactElementSwitchParser.ts +41 -0
- package/src/exactSequenceParser.ts +23 -2
- package/src/fetchCid.test.ts +20 -0
- package/src/fetchCid.ts +121 -0
- package/src/fixedLengthSequenceParser.test.ts +75 -0
- package/src/fixedLengthSequenceParser.ts +28 -1
- package/src/hasExecutable.ts +11 -0
- package/src/highResolutionTimer.ts +49 -0
- package/src/inputReader.test.ts +204 -8
- package/src/inputReader.ts +76 -3
- package/src/inputReaderState.ts +33 -0
- package/src/inspect.ts +9 -0
- package/src/javaKeyStoreParser.test.ts +12 -15
- package/src/javaKeyStoreParser.ts +2 -6
- package/src/jsonParser.test.ts +2 -4
- package/src/jsonParser.ts +46 -62
- package/src/lazyMessageError.test.ts +21 -0
- package/src/lazyMessageError.ts +88 -0
- package/src/leb128Parser.test.ts +173 -0
- package/src/leb128Parser.ts +125 -0
- package/src/lookaheadParser.ts +19 -0
- package/src/negativeLookaheadParser.test.ts +49 -0
- package/src/negativeLookaheadParser.ts +27 -15
- package/src/noStackCaptureOverheadError.test.ts +17 -0
- package/src/noStackCaptureOverheadError.ts +12 -0
- package/src/nonEmptyArrayParser.test.ts +21 -0
- package/src/nonEmptyArrayParser.ts +44 -0
- package/src/optionalParser.ts +3 -2
- package/src/parser.test.ts +203 -31
- package/src/parser.test.ts.md +34 -27
- package/src/parser.test.ts.snap +0 -0
- package/src/parser.ts +172 -45
- package/src/parserAccessorParser.ts +12 -2
- package/src/parserConsumedSequenceParser.ts +26 -17
- package/src/parserContext.test.ts +37 -2
- package/src/parserContext.ts +185 -79
- package/src/parserCreatorCompose.ts +20 -2
- package/src/parserError.ts +144 -61
- package/src/parserImplementationInvariant.ts +3 -3
- package/src/parserInputCompanion.ts +55 -0
- package/src/promiseCompose.ts +17 -3
- package/src/promiseSettled.ts +6 -0
- package/src/separatedArrayParser.test.ts +34 -0
- package/src/separatedArrayParser.ts +55 -0
- package/src/separatedNonEmptyArrayParser.test.ts +117 -0
- package/src/separatedNonEmptyArrayParser.ts +61 -0
- package/src/sequenceBuffer.test.ts +70 -0
- package/src/sequenceBuffer.ts +88 -2
- package/src/sequenceTerminatedSequenceParser.test.ts +58 -0
- package/src/sequenceTerminatedSequenceParser.ts +62 -0
- package/src/sequenceUnparser.ts +2 -2
- package/src/skipParser.ts +7 -5
- package/src/skipToParser.ts +16 -0
- package/src/sliceBoundedParser.test.ts +35 -1
- package/src/sliceBoundedParser.ts +19 -1
- package/src/smali.ts +29 -0
- package/src/smaliParser.test.ts +443 -0
- package/src/smaliParser.test.ts.md +3907 -0
- package/src/smaliParser.test.ts.snap +0 -0
- package/src/smaliParser.ts +3348 -0
- package/src/stringFromAsyncIterable.ts +9 -0
- package/src/terminatedArrayParser.test.ts +258 -0
- package/src/terminatedArrayParser.ts +109 -6
- package/src/toAsyncIterable.ts +7 -0
- package/src/toAsyncIterator.ts +48 -0
- package/src/tupleParser.ts +8 -5
- package/src/uint8Array.ts +2 -3
- package/src/unionParser.test.ts +78 -0
- package/src/unionParser.ts +47 -21
- package/src/unparser.test.ts +18 -34
- package/src/unparser.ts +13 -9
- package/src/unparserContext.ts +9 -13
- package/src/unparserError.ts +2 -1
- package/src/unparserImplementationInvariant.ts +1 -1
- package/src/unparserOutputCompanion.ts +1 -1
- package/src/zip.ts +2 -6
- package/src/zipParser.ts +71 -20
- package/src/zipUnparser.test.ts +19 -19
- package/src/zipUnparser.ts +139 -90
- package/tsconfig.json +7 -1
- package/xo.config.ts +15 -0
- package/.yarn/releases/yarn-4.5.3.cjs +0 -934
- package/build/apk.d.ts +0 -39
- package/build/apkParser.d.ts +0 -16
- package/build/apkParser.js +0 -164
- package/build/apkParser.test.js +0 -22
- package/build/apkUnparser.d.ts +0 -4
- package/build/apkUnparser.js +0 -90
- package/build/apkUnparser.test.js +0 -26
- package/build/arbitraryDosDate.d.ts +0 -2
- package/build/arbitraryDosDate.js +0 -8
- package/build/arbitraryZipEntry.d.ts +0 -3
- package/build/arbitraryZipEntry.js +0 -26
- package/build/createDisjunctionParser.d.ts +0 -2
- package/build/createDisjunctionParser.js +0 -47
- package/build/createExactParser.d.ts +0 -2
- package/build/createExactParser.js +0 -12
- package/build/createSequentialUnionParser.d.ts +0 -2
- package/build/createSequentialUnionParser.js +0 -69
- package/build/fixedLengthChunkParser.d.ts +0 -2
- package/build/fixedLengthChunkParser.js +0 -12
- package/build/fixedLengthParser.d.ts +0 -2
- package/build/fixedLengthParser.js +0 -12
- package/build/inputChunkBuffer.d.ts +0 -15
- package/build/inputChunkBuffer.js +0 -40
- package/build/inputChunkBuffer.test.js +0 -34
- package/build/inputCompanion.d.ts +0 -18
- package/build/inputCompanion.js +0 -28
- package/build/invariantDefined.d.ts +0 -1
- package/build/invariantDefined.js +0 -5
- package/build/invariantIdentity.d.ts +0 -3
- package/build/invariantIdentity.js +0 -5
- package/build/javaKeystoreParser.d.ts +0 -2
- package/build/javaKeystoreParser.js +0 -7
- package/build/jsonParser2.d.ts +0 -3
- package/build/jsonParser2.js +0 -52
- package/build/jsonParser2.test.js +0 -22
- package/build/negativeLookahead.d.ts +0 -2
- package/build/negativeLookahead.js +0 -18
- package/build/parserCompose.d.ts +0 -3
- package/build/parserCompose.js +0 -7
- package/build/parserImplementationInvariantInvariant.d.ts +0 -3
- package/build/parserImplementationInvariantInvariant.js +0 -15
- package/build/parserInvariant.d.ts +0 -4
- package/build/parserInvariant.js +0 -11
- package/build/promiseFish.d.ts +0 -1
- package/build/promiseFish.js +0 -3
- package/build/sequenceParser.d.ts +0 -3
- package/build/sequenceParser.js +0 -10
- package/build/terminatedSequenceParser.d.ts +0 -2
- package/build/terminatedSequenceParser.js +0 -24
- package/build/unparserInputCompanion.d.ts +0 -15
- package/build/unparserInputCompanion.js +0 -13
- package/build/zipEntry.d.ts +0 -28
- package/build/zipFile.d.ts +0 -32
- package/build/zipFileEntry.d.ts +0 -6
- package/src/apk.ts +0 -48
- package/src/apkParser.test.ts +0 -30
- package/src/apkParser.test.ts.snap +0 -0
- package/src/apkParser.ts +0 -392
- package/src/apkUnparser.test.ts +0 -37
- package/src/apkUnparser.ts +0 -120
- package/src/invariantDefined.ts +0 -6
- package/src/invariantIdentity.ts +0 -8
- /package/build/{apk.js → androidPackage.js} +0 -0
- /package/build/{apkParser.test.d.ts → androidPackageParser.test.d.ts} +0 -0
- /package/build/{apkUnparser.test.d.ts → androidPackageUnparser.test.d.ts} +0 -0
- /package/build/{arbitraryDosPermissions.d.ts → dalvikBytecodeUnparser.test.d.ts} +0 -0
- /package/build/{arbitraryDosPermissions.js → dalvikExecutableParser.test.d.ts} +0 -0
- /package/build/{inputChunkBuffer.test.d.ts → dalvikExecutableParserAgainstSmaliParser.test.d.ts} +0 -0
- /package/build/{jsonParser2.test.d.ts → dalvikExecutableUnparser.test.d.ts} +0 -0
- /package/build/{parserParsingInvariant.d.ts → elementTerminatedArrayParser.test.d.ts} +0 -0
- /package/build/{parserParsingInvariant.js → elementTerminatedSequenceArrayParser.test.d.ts} +0 -0
- /package/build/{zipEntry.js → elementTerminatedSequenceParser.test.d.ts} +0 -0
- /package/build/{zipFile.js → fetchCid.test.d.ts} +0 -0
- /package/build/{zipFileEntry.js → fixedLengthSequenceParser.test.d.ts} +0 -0
|
@@ -0,0 +1,3348 @@
|
|
|
1
|
+
import invariant from 'invariant';
|
|
2
|
+
import { type Simplify } from 'type-fest';
|
|
3
|
+
import { type DalvikBytecode, type DalvikBytecodeOperation, dalvikBytecodeOperationCompanion } from './dalvikBytecodeParser.js';
|
|
4
|
+
import {
|
|
5
|
+
type DalvikExecutableAccessFlags, dalvikExecutableAccessFlagsDefault, type DalvikExecutableAnnotation, type DalvikExecutableClassAnnotations, type DalvikExecutableClassData, type DalvikExecutableClassDefinition, type DalvikExecutableClassMethodAnnotation, type DalvikExecutableClassParameterAnnotation, type DalvikExecutableCode, type DalvikExecutableDebugInfo, type DalvikExecutableEncodedValue, type DalvikExecutableField, dalvikExecutableFieldEquals, type DalvikExecutableFieldWithAccess, type DalvikExecutableMethod, dalvikExecutableMethodEquals, type DalvikExecutableMethodWithAccess, type DalvikExecutablePrototype, isDalvikExecutableField, isDalvikExecutableMethod,
|
|
6
|
+
} from './dalvikExecutable.js';
|
|
7
|
+
import { createExactSequenceParser } from './exactSequenceParser.js';
|
|
8
|
+
import { cloneParser, type Parser, setParserName } from './parser.js';
|
|
9
|
+
import { type ParserContext } from './parserContext.js';
|
|
10
|
+
import { promiseCompose } from './promiseCompose.js';
|
|
11
|
+
import { createTupleParser } from './tupleParser.js';
|
|
12
|
+
import { createUnionParser } from './unionParser.js';
|
|
13
|
+
import { createArrayParser } from './arrayParser.js';
|
|
14
|
+
import { jsonNumberParser, jsonStringParser } from './jsonParser.js';
|
|
15
|
+
import { createNonEmptyArrayParser } from './nonEmptyArrayParser.js';
|
|
16
|
+
import { createOptionalParser } from './optionalParser.js';
|
|
17
|
+
import { createNegativeLookaheadParser } from './negativeLookaheadParser.js';
|
|
18
|
+
import { createSeparatedArrayParser } from './separatedArrayParser.js';
|
|
19
|
+
import { smaliMemberNameParser, smaliTypeDescriptorParser } from './dalvikExecutableParser/stringSyntaxParser.js';
|
|
20
|
+
import { createDisjunctionParser } from './disjunctionParser.js';
|
|
21
|
+
import { formatSizes } from './dalvikBytecodeParser/formatSizes.js';
|
|
22
|
+
import { operationFormats } from './dalvikBytecodeParser/operationFormats.js';
|
|
23
|
+
import { createSeparatedNonEmptyArrayParser } from './separatedNonEmptyArrayParser.js';
|
|
24
|
+
import { parserCreatorCompose } from './parserCreatorCompose.js';
|
|
25
|
+
import { type IndexIntoMethodIds } from './dalvikExecutableParser/typedNumbers.js';
|
|
26
|
+
import { createDebugLogInputParser } from './debugLogInputParser.js';
|
|
27
|
+
import { createDebugLogParser } from './debugLogParser.js';
|
|
28
|
+
import { createElementParser } from './elementParser.js';
|
|
29
|
+
import { createTerminatedArrayParser } from './terminatedArrayParser.js';
|
|
30
|
+
import { createParserAccessorParser } from './parserAccessorParser.js';
|
|
31
|
+
|
|
32
|
+
function shortyFromLongy(longy: string): string {
|
|
33
|
+
if (longy.startsWith('[')) {
|
|
34
|
+
return 'L';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return longy.slice(0, 1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getOperationFormatSize(operation: SmaliCodeOperation): number {
|
|
41
|
+
if (operation.operation === 'packed-switch-payload') {
|
|
42
|
+
return (operation.branchOffsetIndices.length * 2) + 4;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (operation.operation === 'sparse-switch-payload') {
|
|
46
|
+
return (operation.branchOffsetIndices.length * 4) + 2;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (operation.operation === 'fill-array-data-payload') {
|
|
50
|
+
const dataSize = operation.data.length; // in bytes
|
|
51
|
+
const paddingSize = dataSize % 2; // 1 if odd, 0 if even
|
|
52
|
+
const totalBytes = 8 + dataSize + paddingSize; // header (8 bytes) + data + padding
|
|
53
|
+
return totalBytes / 2; // Convert to code units (1 code unit = 2 bytes)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const operationFormat = operationFormats[operation.operation as keyof typeof operationFormats];
|
|
57
|
+
invariant(operationFormat, 'Unknown operation format for "%s" (operation: %o)', operation.operation, operation);
|
|
58
|
+
|
|
59
|
+
const operationSize = formatSizes[operationFormat];
|
|
60
|
+
invariant(operationSize, 'Unknown operation size for format %s of operation %s', operationFormat, operation.operation);
|
|
61
|
+
|
|
62
|
+
return operationSize;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Helper function to convert raw annotation element values to tagged encoded values
|
|
66
|
+
function convertToTaggedEncodedValue(wrappedValue: SmaliAnnotationElementValue): DalvikExecutableEncodedValue {
|
|
67
|
+
const { kind, value } = wrappedValue;
|
|
68
|
+
|
|
69
|
+
// Handle type descriptors
|
|
70
|
+
if (kind === 'type') {
|
|
71
|
+
if (Array.isArray(value)) {
|
|
72
|
+
// Array of type descriptors
|
|
73
|
+
return { type: 'array', value: value.map(v => ({ type: 'type', value: v })) };
|
|
74
|
+
}
|
|
75
|
+
// Single type descriptor
|
|
76
|
+
return { type: 'type', value };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Handle string literals
|
|
80
|
+
if (kind === 'string') {
|
|
81
|
+
if (Array.isArray(value)) {
|
|
82
|
+
// Array of strings
|
|
83
|
+
return { type: 'array', value: value.map(v => ({ type: 'string', value: v })) };
|
|
84
|
+
}
|
|
85
|
+
// Single string
|
|
86
|
+
return { type: 'string', value };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Handle enum values
|
|
90
|
+
if (kind === 'enum') {
|
|
91
|
+
if (Array.isArray(value)) {
|
|
92
|
+
// Array of enum values
|
|
93
|
+
return { type: 'array', value: value.map(v => ({ type: 'enum', value: v })) };
|
|
94
|
+
}
|
|
95
|
+
// Single enum value
|
|
96
|
+
return { type: 'enum', value };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Handle raw values (everything else)
|
|
100
|
+
// Handle null
|
|
101
|
+
if (value === null) {
|
|
102
|
+
return { type: 'null', value: null };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Handle boolean
|
|
106
|
+
if (typeof value === 'boolean') {
|
|
107
|
+
return { type: 'boolean', value };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Handle numbers - we need to determine the type based on context
|
|
111
|
+
// For annotation elements from smali, we'll use 'int' as the default
|
|
112
|
+
if (typeof value === 'number') {
|
|
113
|
+
// Check if it's a float or integer
|
|
114
|
+
if (!Number.isInteger(value)) {
|
|
115
|
+
return { type: 'float', value };
|
|
116
|
+
}
|
|
117
|
+
// For integers, default to 'int' type
|
|
118
|
+
// The actual byte/short/int distinction would need more context
|
|
119
|
+
return { type: 'int', value };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Handle bigint
|
|
123
|
+
if (typeof value === 'bigint') {
|
|
124
|
+
return { type: 'long', value };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Handle DalvikExecutableField (including enums)
|
|
128
|
+
if (isDalvikExecutableField(value)) {
|
|
129
|
+
// Note: We can't distinguish between 'field' and 'enum' without more context
|
|
130
|
+
// Default to 'field' type - the context in smali might help distinguish later
|
|
131
|
+
return { type: 'field', value };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Handle DalvikExecutableMethod
|
|
135
|
+
if (isDalvikExecutableMethod(value)) {
|
|
136
|
+
return { type: 'method', value };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Handle DalvikExecutablePrototype (method type)
|
|
140
|
+
if (typeof value === 'object' && value !== null && 'returnType' in value && 'parameters' in value && 'shorty' in value) {
|
|
141
|
+
return { type: 'methodType', value: value as DalvikExecutablePrototype };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Handle annotations (subannotations)
|
|
145
|
+
if (typeof value === 'object' && value !== null && 'type' in value && 'elements' in value) {
|
|
146
|
+
const subannotation = value as SmaliSubannotation;
|
|
147
|
+
// Convert subannotation to DalvikExecutableAnnotation
|
|
148
|
+
const annotation: DalvikExecutableAnnotation = {
|
|
149
|
+
type: subannotation.type,
|
|
150
|
+
visibility: 'build', // Subannotations default to 'build' visibility
|
|
151
|
+
elements: subannotation.elements.map(element => ({
|
|
152
|
+
name: element.name,
|
|
153
|
+
value: convertToTaggedEncodedValue(element.value),
|
|
154
|
+
})),
|
|
155
|
+
};
|
|
156
|
+
return { type: 'annotation', value: annotation };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Handle arrays
|
|
160
|
+
if (Array.isArray(value)) {
|
|
161
|
+
return { type: 'array', value: value.map(v => convertToTaggedEncodedValue({ kind: 'raw', value: v })) };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Fallback - this shouldn't happen in well-formed smali
|
|
165
|
+
throw new Error(`Cannot convert value to tagged encoded value: ${JSON.stringify(value)}`);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
const smaliNewlinesParser: Parser<void, string> = promiseCompose(
|
|
170
|
+
createNonEmptyArrayParser(createExactSequenceParser('\n')),
|
|
171
|
+
_newlines => undefined,
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const smaliSingleWhitespaceParser = createExactSequenceParser(' ');
|
|
175
|
+
|
|
176
|
+
const smaliWhitespaceParser: Parser<void, string> = promiseCompose(
|
|
177
|
+
createArrayParser(smaliSingleWhitespaceParser),
|
|
178
|
+
_indentation => undefined,
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
const smaliSingleIndentationParser = createExactSequenceParser(' ');
|
|
182
|
+
|
|
183
|
+
const smaliIndentationParser: Parser<void, string> = promiseCompose(
|
|
184
|
+
createArrayParser(smaliSingleIndentationParser),
|
|
185
|
+
_indentation => undefined,
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
export const smaliCommentParser: Parser<string, string> = promiseCompose(
|
|
189
|
+
createTupleParser([
|
|
190
|
+
createExactSequenceParser('#'),
|
|
191
|
+
(async (parserContext: ParserContext<string, string>) => {
|
|
192
|
+
const characters: string[] = [];
|
|
193
|
+
|
|
194
|
+
while (true) {
|
|
195
|
+
const character = await parserContext.peek(0);
|
|
196
|
+
|
|
197
|
+
parserContext.invariant(character !== undefined, 'Unexpected end of input');
|
|
198
|
+
|
|
199
|
+
invariant(character !== undefined, 'Unexpected end of input');
|
|
200
|
+
|
|
201
|
+
if (character !== '\n') {
|
|
202
|
+
characters.push(character);
|
|
203
|
+
|
|
204
|
+
parserContext.skip(1);
|
|
205
|
+
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
parserContext.skip(1);
|
|
210
|
+
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return characters.join('');
|
|
215
|
+
}),
|
|
216
|
+
]),
|
|
217
|
+
([
|
|
218
|
+
_hash,
|
|
219
|
+
comment,
|
|
220
|
+
]) => comment,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const smaliIndentedCommentParser: Parser<string, string> = promiseCompose(
|
|
224
|
+
createTupleParser([
|
|
225
|
+
createNonEmptyArrayParser(smaliSingleIndentationParser),
|
|
226
|
+
smaliCommentParser,
|
|
227
|
+
]),
|
|
228
|
+
([
|
|
229
|
+
_indentation,
|
|
230
|
+
comment,
|
|
231
|
+
]) => comment,
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const smaliCommentsOrNewlinesParser: Parser<string[], string> = promiseCompose(
|
|
235
|
+
createArrayParser(createUnionParser<undefined | string, string, string>([
|
|
236
|
+
smaliNewlinesParser,
|
|
237
|
+
smaliIndentedCommentParser,
|
|
238
|
+
smaliCommentParser,
|
|
239
|
+
])),
|
|
240
|
+
newlinesOrComments => newlinesOrComments.filter((newlineOrComment): newlineOrComment is string => typeof newlineOrComment === 'string'),
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const smaliLineEndPraser: Parser<undefined | string, string> = promiseCompose(
|
|
244
|
+
createTupleParser([
|
|
245
|
+
createOptionalParser(smaliWhitespaceParser),
|
|
246
|
+
createUnionParser<undefined | string, string, string>([
|
|
247
|
+
smaliNewlinesParser,
|
|
248
|
+
smaliCommentParser,
|
|
249
|
+
]),
|
|
250
|
+
]),
|
|
251
|
+
([
|
|
252
|
+
_optionalWhitespace,
|
|
253
|
+
newlineOrComment,
|
|
254
|
+
]) => newlineOrComment,
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
const smaliIdentifierParser: Parser<string, string> = async (parserContext: ParserContext<string, string>) => {
|
|
258
|
+
const characters: string[] = [];
|
|
259
|
+
|
|
260
|
+
while (true) {
|
|
261
|
+
const character = await parserContext.peek(0);
|
|
262
|
+
|
|
263
|
+
parserContext.invariant(character !== undefined, 'Unexpected end of input');
|
|
264
|
+
|
|
265
|
+
invariant(character !== undefined, 'Unexpected end of input');
|
|
266
|
+
|
|
267
|
+
if (
|
|
268
|
+
character === '_'
|
|
269
|
+
|| (
|
|
270
|
+
character >= 'a' && character <= 'z'
|
|
271
|
+
)
|
|
272
|
+
|| (
|
|
273
|
+
character >= 'A' && character <= 'Z'
|
|
274
|
+
)
|
|
275
|
+
|| (
|
|
276
|
+
character >= '0' && character <= '9'
|
|
277
|
+
)
|
|
278
|
+
) {
|
|
279
|
+
parserContext.skip(1);
|
|
280
|
+
|
|
281
|
+
characters.push(character);
|
|
282
|
+
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
parserContext.invariant(characters.length > 0, 'Expected at least one character');
|
|
287
|
+
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return characters.join('');
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
setParserName(smaliIdentifierParser, 'smaliIdentifierParser');
|
|
295
|
+
|
|
296
|
+
const elementParser = createElementParser<string>();
|
|
297
|
+
|
|
298
|
+
const smaliHexNumberParser: Parser<number | bigint, string> = promiseCompose(
|
|
299
|
+
createTupleParser([
|
|
300
|
+
createOptionalParser(createExactSequenceParser('-')),
|
|
301
|
+
createExactSequenceParser('0x'),
|
|
302
|
+
createArrayParser(parserCreatorCompose(
|
|
303
|
+
() => elementParser,
|
|
304
|
+
character => async parserContext => {
|
|
305
|
+
parserContext.invariant(
|
|
306
|
+
(
|
|
307
|
+
(character >= '0' && character <= '9')
|
|
308
|
+
|| (character >= 'a' && character <= 'f')
|
|
309
|
+
|| (character >= 'A' && character <= 'F')
|
|
310
|
+
),
|
|
311
|
+
'Expected "0" to "9", "a" to "f", "A" to "F", got "%s"',
|
|
312
|
+
character,
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
return character;
|
|
316
|
+
},
|
|
317
|
+
)()),
|
|
318
|
+
createOptionalParser(createExactSequenceParser('L')),
|
|
319
|
+
]),
|
|
320
|
+
([
|
|
321
|
+
optionalMinus,
|
|
322
|
+
_0x,
|
|
323
|
+
valueCharacters,
|
|
324
|
+
optionalL,
|
|
325
|
+
]) => {
|
|
326
|
+
const value = valueCharacters.join('');
|
|
327
|
+
|
|
328
|
+
// If the 'L' suffix is present, use BigInt for long values
|
|
329
|
+
if (optionalL) {
|
|
330
|
+
const sign = optionalMinus ? -1n : 1n;
|
|
331
|
+
return sign * BigInt('0x' + value);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const sign = optionalMinus ? -1 : 1;
|
|
335
|
+
return sign * Number.parseInt(value, 16);
|
|
336
|
+
},
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
const smaliNumberParser = createUnionParser<number, string>([
|
|
340
|
+
promiseCompose(
|
|
341
|
+
createTupleParser([
|
|
342
|
+
createNegativeLookaheadParser(createUnionParser([
|
|
343
|
+
createExactSequenceParser('0x'),
|
|
344
|
+
createExactSequenceParser('-0x'),
|
|
345
|
+
])),
|
|
346
|
+
jsonNumberParser,
|
|
347
|
+
createOptionalParser(createUnionParser([
|
|
348
|
+
createExactSequenceParser('f'),
|
|
349
|
+
createExactSequenceParser('F'),
|
|
350
|
+
])),
|
|
351
|
+
]),
|
|
352
|
+
([
|
|
353
|
+
_not0x,
|
|
354
|
+
number,
|
|
355
|
+
optionalFloatSuffix,
|
|
356
|
+
]) => {
|
|
357
|
+
// If there's an 'f' or 'F' suffix, convert to 32-bit float precision
|
|
358
|
+
// to match what would be stored in a DEX file
|
|
359
|
+
if (optionalFloatSuffix) {
|
|
360
|
+
const float32Array = new Float32Array(1);
|
|
361
|
+
float32Array[0] = number;
|
|
362
|
+
return float32Array[0];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return number;
|
|
366
|
+
},
|
|
367
|
+
),
|
|
368
|
+
promiseCompose(
|
|
369
|
+
smaliHexNumberParser,
|
|
370
|
+
value => {
|
|
371
|
+
// For smaliNumberParser, we need to ensure we return a number
|
|
372
|
+
// BigInt values from hex parser should be converted if they fit in number range
|
|
373
|
+
if (typeof value === 'bigint') {
|
|
374
|
+
// This shouldn't happen in contexts where smaliNumberParser is used
|
|
375
|
+
// (registers, line numbers, etc) but if it does, convert to number
|
|
376
|
+
return Number(value);
|
|
377
|
+
}
|
|
378
|
+
return value;
|
|
379
|
+
},
|
|
380
|
+
),
|
|
381
|
+
]);
|
|
382
|
+
|
|
383
|
+
setParserName(smaliNumberParser, 'smaliNumberParser');
|
|
384
|
+
|
|
385
|
+
// Parser for field initial values that can include BigInt
|
|
386
|
+
const smaliFieldValueParser = createUnionParser<number | bigint, string>([
|
|
387
|
+
promiseCompose(
|
|
388
|
+
createTupleParser([
|
|
389
|
+
createNegativeLookaheadParser(createUnionParser([
|
|
390
|
+
createExactSequenceParser('0x'),
|
|
391
|
+
createExactSequenceParser('-0x'),
|
|
392
|
+
])),
|
|
393
|
+
jsonNumberParser,
|
|
394
|
+
createOptionalParser(createUnionParser([
|
|
395
|
+
createExactSequenceParser('f'),
|
|
396
|
+
createExactSequenceParser('F'),
|
|
397
|
+
])),
|
|
398
|
+
]),
|
|
399
|
+
([
|
|
400
|
+
_not0x,
|
|
401
|
+
number,
|
|
402
|
+
optionalFloatSuffix,
|
|
403
|
+
]) => {
|
|
404
|
+
// If there's an 'f' or 'F' suffix, convert to 32-bit float precision
|
|
405
|
+
// to match what would be stored in a DEX file
|
|
406
|
+
if (optionalFloatSuffix) {
|
|
407
|
+
const float32Array = new Float32Array(1);
|
|
408
|
+
float32Array[0] = number;
|
|
409
|
+
return float32Array[0];
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
return number;
|
|
413
|
+
},
|
|
414
|
+
),
|
|
415
|
+
smaliHexNumberParser,
|
|
416
|
+
]);
|
|
417
|
+
|
|
418
|
+
const smaliQuotedStringParser: Parser<string, string> = promiseCompose(
|
|
419
|
+
jsonStringParser,
|
|
420
|
+
string => string.replaceAll(String.raw`\'`, '\''),
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
// Parser for smali character literals (e.g., 'a', ':', '\'', '\\')
|
|
424
|
+
const smaliCharacterLiteralParser: Parser<number, string> = promiseCompose(
|
|
425
|
+
createTupleParser([
|
|
426
|
+
createExactSequenceParser('\''),
|
|
427
|
+
createDisjunctionParser<string, string>([
|
|
428
|
+
// Handle escape sequences (must come before regular characters)
|
|
429
|
+
promiseCompose(createExactSequenceParser(String.raw`\\`), () => '\\'),
|
|
430
|
+
promiseCompose(createExactSequenceParser(String.raw`\'`), () => '\''),
|
|
431
|
+
promiseCompose(createExactSequenceParser(String.raw`\"`), () => '"'),
|
|
432
|
+
promiseCompose(createExactSequenceParser(String.raw`\n`), () => '\n'),
|
|
433
|
+
promiseCompose(createExactSequenceParser(String.raw`\r`), () => '\r'),
|
|
434
|
+
promiseCompose(createExactSequenceParser(String.raw`\t`), () => '\t'),
|
|
435
|
+
promiseCompose(createExactSequenceParser(String.raw`\b`), () => '\b'),
|
|
436
|
+
promiseCompose(createExactSequenceParser(String.raw`\f`), () => '\f'),
|
|
437
|
+
// Handle regular characters (not a single quote)
|
|
438
|
+
parserCreatorCompose(
|
|
439
|
+
() => createElementParser<string>(),
|
|
440
|
+
character => async parserContext => {
|
|
441
|
+
parserContext.invariant(character !== '\'', 'Unexpected single quote');
|
|
442
|
+
return character;
|
|
443
|
+
},
|
|
444
|
+
)(),
|
|
445
|
+
]),
|
|
446
|
+
createExactSequenceParser('\''),
|
|
447
|
+
]),
|
|
448
|
+
([, character]) => character.charCodeAt(0),
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
setParserName(smaliCharacterLiteralParser, 'smaliCharacterLiteralParser');
|
|
452
|
+
|
|
453
|
+
// Parser that matches identifier continuation characters (letters, digits, $, -, _)
|
|
454
|
+
const smaliIdentifierContinuationParser: Parser<string, string> = async (parserContext: ParserContext<string, string>) => {
|
|
455
|
+
const character = await parserContext.peek(0);
|
|
456
|
+
|
|
457
|
+
parserContext.invariant(character !== undefined, 'Unexpected end of input');
|
|
458
|
+
|
|
459
|
+
invariant(character !== undefined, 'Unexpected end of input');
|
|
460
|
+
|
|
461
|
+
parserContext.invariant(
|
|
462
|
+
(character >= 'a' && character <= 'z')
|
|
463
|
+
|| (character >= 'A' && character <= 'Z')
|
|
464
|
+
|| (character >= '0' && character <= '9')
|
|
465
|
+
|| character === '$'
|
|
466
|
+
|| character === '-'
|
|
467
|
+
|| character === '_',
|
|
468
|
+
'Expected identifier continuation character, got "%s"',
|
|
469
|
+
character,
|
|
470
|
+
);
|
|
471
|
+
|
|
472
|
+
parserContext.skip(1);
|
|
473
|
+
|
|
474
|
+
return character;
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
setParserName(smaliIdentifierContinuationParser, 'smaliIdentifierContinuationParser');
|
|
478
|
+
|
|
479
|
+
// Helper to create an access flag parser with word boundary check
|
|
480
|
+
const createAccessFlagParser = (keyword: string): Parser<typeof keyword, string> => promiseCompose(
|
|
481
|
+
createTupleParser([
|
|
482
|
+
createExactSequenceParser(keyword),
|
|
483
|
+
createNegativeLookaheadParser(smaliIdentifierContinuationParser),
|
|
484
|
+
]),
|
|
485
|
+
([flag]) => flag,
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
const smaliAccessFlagsParser: Parser<DalvikExecutableAccessFlags, string> = promiseCompose(
|
|
489
|
+
createSeparatedArrayParser(
|
|
490
|
+
createUnionParser<keyof DalvikExecutableAccessFlags | 'declared-synchronized', string>([
|
|
491
|
+
createAccessFlagParser('public'),
|
|
492
|
+
createAccessFlagParser('protected'),
|
|
493
|
+
createAccessFlagParser('private'),
|
|
494
|
+
createAccessFlagParser('final'),
|
|
495
|
+
createAccessFlagParser('bridge'),
|
|
496
|
+
createAccessFlagParser('synthetic'),
|
|
497
|
+
createAccessFlagParser('varargs'),
|
|
498
|
+
createAccessFlagParser('static'),
|
|
499
|
+
createAccessFlagParser('constructor'),
|
|
500
|
+
createAccessFlagParser('abstract'),
|
|
501
|
+
createAccessFlagParser('native'),
|
|
502
|
+
createAccessFlagParser('volatile'),
|
|
503
|
+
createAccessFlagParser('transient'),
|
|
504
|
+
createAccessFlagParser('synchronized'),
|
|
505
|
+
createAccessFlagParser('declared-synchronized'),
|
|
506
|
+
createAccessFlagParser('strict'),
|
|
507
|
+
createAccessFlagParser('interface'),
|
|
508
|
+
createAccessFlagParser('annotation'),
|
|
509
|
+
createAccessFlagParser('enum'),
|
|
510
|
+
]),
|
|
511
|
+
smaliSingleWhitespaceParser,
|
|
512
|
+
),
|
|
513
|
+
accessFlagNames => {
|
|
514
|
+
const accessFlags = dalvikExecutableAccessFlagsDefault();
|
|
515
|
+
|
|
516
|
+
for (const accessFlagName of accessFlagNames) {
|
|
517
|
+
if (accessFlagName === 'declared-synchronized') {
|
|
518
|
+
accessFlags.declaredSynchronized = true;
|
|
519
|
+
} else {
|
|
520
|
+
accessFlags[accessFlagName] = true;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return accessFlags;
|
|
525
|
+
},
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
const smaliClassDeclarationParser: Parser<Pick<DalvikExecutableClassDefinition<unknown>, 'accessFlags' | 'class'>, string> = promiseCompose(
|
|
529
|
+
createTupleParser([
|
|
530
|
+
smaliCommentsOrNewlinesParser,
|
|
531
|
+
createExactSequenceParser('.class '),
|
|
532
|
+
createOptionalParser(promiseCompose(
|
|
533
|
+
createTupleParser([
|
|
534
|
+
smaliAccessFlagsParser,
|
|
535
|
+
smaliSingleWhitespaceParser,
|
|
536
|
+
]),
|
|
537
|
+
([
|
|
538
|
+
accessFlags,
|
|
539
|
+
]) => accessFlags,
|
|
540
|
+
)),
|
|
541
|
+
smaliTypeDescriptorParser,
|
|
542
|
+
smaliLineEndPraser,
|
|
543
|
+
]),
|
|
544
|
+
([
|
|
545
|
+
_commentsOrNewlines,
|
|
546
|
+
_dotClass,
|
|
547
|
+
accessFlags = dalvikExecutableAccessFlagsDefault(),
|
|
548
|
+
classPath,
|
|
549
|
+
]) => ({
|
|
550
|
+
accessFlags,
|
|
551
|
+
class: classPath,
|
|
552
|
+
}),
|
|
553
|
+
);
|
|
554
|
+
|
|
555
|
+
setParserName(smaliClassDeclarationParser, 'smaliClassDeclarationParser');
|
|
556
|
+
|
|
557
|
+
const smaliSuperDeclarationParser: Parser<Pick<DalvikExecutableClassDefinition<unknown>, 'superclass'>, string> = promiseCompose(
|
|
558
|
+
createTupleParser([
|
|
559
|
+
createExactSequenceParser('.super '),
|
|
560
|
+
smaliTypeDescriptorParser,
|
|
561
|
+
smaliLineEndPraser,
|
|
562
|
+
]),
|
|
563
|
+
([
|
|
564
|
+
_super,
|
|
565
|
+
superclass,
|
|
566
|
+
_newline,
|
|
567
|
+
]) => ({
|
|
568
|
+
superclass,
|
|
569
|
+
}),
|
|
570
|
+
);
|
|
571
|
+
|
|
572
|
+
setParserName(smaliSuperDeclarationParser, 'smaliSuperDeclarationParser');
|
|
573
|
+
|
|
574
|
+
const smaliInterfaceDeclarationParser: Parser<string, string> = promiseCompose(
|
|
575
|
+
createTupleParser([
|
|
576
|
+
createExactSequenceParser('.implements '),
|
|
577
|
+
smaliTypeDescriptorParser,
|
|
578
|
+
smaliLineEndPraser,
|
|
579
|
+
]),
|
|
580
|
+
([
|
|
581
|
+
_interface,
|
|
582
|
+
interface_,
|
|
583
|
+
_newline,
|
|
584
|
+
]) => interface_,
|
|
585
|
+
);
|
|
586
|
+
|
|
587
|
+
setParserName(smaliInterfaceDeclarationParser, 'smaliInterfaceDeclarationParser');
|
|
588
|
+
|
|
589
|
+
const smaliSourceDeclarationParser: Parser<Pick<DalvikExecutableClassDefinition<unknown>, 'sourceFile'>, string> = promiseCompose(
|
|
590
|
+
createTupleParser([
|
|
591
|
+
createExactSequenceParser('.source '),
|
|
592
|
+
smaliQuotedStringParser,
|
|
593
|
+
smaliLineEndPraser,
|
|
594
|
+
]),
|
|
595
|
+
([
|
|
596
|
+
_source,
|
|
597
|
+
sourceFile,
|
|
598
|
+
_newline,
|
|
599
|
+
]) => ({
|
|
600
|
+
sourceFile,
|
|
601
|
+
}),
|
|
602
|
+
);
|
|
603
|
+
|
|
604
|
+
// Wrapper type to distinguish different value types in smali annotation elements
|
|
605
|
+
type SmaliAnnotationElementValue =
|
|
606
|
+
| { kind: 'type'; value: string | string[] }
|
|
607
|
+
| { kind: 'string'; value: string | string[] }
|
|
608
|
+
| { kind: 'enum'; value: DalvikExecutableField | DalvikExecutableField[] }
|
|
609
|
+
| { kind: 'raw'; value: unknown };
|
|
610
|
+
|
|
611
|
+
type SmaliAnnotationElement = {
|
|
612
|
+
name: string;
|
|
613
|
+
value: SmaliAnnotationElementValue;
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
const smaliEnumValueParser: Parser<DalvikExecutableField, string> = promiseCompose(
|
|
617
|
+
createTupleParser([
|
|
618
|
+
createExactSequenceParser('.enum '),
|
|
619
|
+
smaliTypeDescriptorParser,
|
|
620
|
+
createExactSequenceParser('->'),
|
|
621
|
+
smaliMemberNameParser,
|
|
622
|
+
createExactSequenceParser(':'),
|
|
623
|
+
smaliTypeDescriptorParser,
|
|
624
|
+
]),
|
|
625
|
+
([
|
|
626
|
+
_enum,
|
|
627
|
+
classType,
|
|
628
|
+
_arrow,
|
|
629
|
+
fieldName,
|
|
630
|
+
_colon,
|
|
631
|
+
fieldType,
|
|
632
|
+
]) => ({
|
|
633
|
+
class: classType,
|
|
634
|
+
type: fieldType,
|
|
635
|
+
name: fieldName,
|
|
636
|
+
}),
|
|
637
|
+
);
|
|
638
|
+
|
|
639
|
+
setParserName(smaliEnumValueParser, 'smaliEnumValueParser');
|
|
640
|
+
|
|
641
|
+
const smaliMethodPrototypeParser: Parser<DalvikExecutablePrototype, string> = promiseCompose(
|
|
642
|
+
createTupleParser([
|
|
643
|
+
createExactSequenceParser('('),
|
|
644
|
+
createArrayParser(smaliTypeDescriptorParser),
|
|
645
|
+
createExactSequenceParser(')'),
|
|
646
|
+
smaliTypeDescriptorParser,
|
|
647
|
+
]),
|
|
648
|
+
([
|
|
649
|
+
_openParenthesis,
|
|
650
|
+
parameters,
|
|
651
|
+
_closeParenthesis,
|
|
652
|
+
returnType,
|
|
653
|
+
]) => ({
|
|
654
|
+
parameters,
|
|
655
|
+
returnType,
|
|
656
|
+
shorty: shortyFromLongy(returnType) + parameters.map(parameter => {
|
|
657
|
+
if (parameter === 'V') {
|
|
658
|
+
return '';
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
return shortyFromLongy(parameter);
|
|
662
|
+
}).join(''),
|
|
663
|
+
}),
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
setParserName(smaliMethodPrototypeParser, 'smaliMethodPrototypeParser');
|
|
667
|
+
|
|
668
|
+
const smaliParametersMethodParser: Parser<DalvikExecutableMethod, string> = promiseCompose(
|
|
669
|
+
createTupleParser([
|
|
670
|
+
smaliTypeDescriptorParser,
|
|
671
|
+
createExactSequenceParser('->'),
|
|
672
|
+
smaliMemberNameParser,
|
|
673
|
+
smaliMethodPrototypeParser,
|
|
674
|
+
]),
|
|
675
|
+
([
|
|
676
|
+
classPath,
|
|
677
|
+
_separator,
|
|
678
|
+
methodName,
|
|
679
|
+
prototype,
|
|
680
|
+
]) => ({
|
|
681
|
+
class: classPath,
|
|
682
|
+
prototype,
|
|
683
|
+
name: methodName,
|
|
684
|
+
}),
|
|
685
|
+
);
|
|
686
|
+
|
|
687
|
+
setParserName(smaliParametersMethodParser, 'smaliParametersMethodParser');
|
|
688
|
+
|
|
689
|
+
type SmaliSubannotation = {
|
|
690
|
+
type: string;
|
|
691
|
+
elements: SmaliAnnotationElement[];
|
|
692
|
+
};
|
|
693
|
+
|
|
694
|
+
// Forward declaration to handle circular reference
|
|
695
|
+
let smaliSubannotationParser: Parser<SmaliSubannotation, string>;
|
|
696
|
+
|
|
697
|
+
const smaliAnnotationElementParser: Parser<SmaliAnnotationElement, string> = promiseCompose(
|
|
698
|
+
createTupleParser([
|
|
699
|
+
smaliIndentationParser,
|
|
700
|
+
smaliIdentifierParser,
|
|
701
|
+
createExactSequenceParser(' = '),
|
|
702
|
+
createDisjunctionParser([
|
|
703
|
+
promiseCompose(
|
|
704
|
+
createParserAccessorParser(() => smaliSubannotationParser),
|
|
705
|
+
value => ({ kind: 'raw' as const, value }),
|
|
706
|
+
),
|
|
707
|
+
promiseCompose(
|
|
708
|
+
smaliEnumValueParser,
|
|
709
|
+
value => ({ kind: 'enum' as const, value }),
|
|
710
|
+
),
|
|
711
|
+
promiseCompose(
|
|
712
|
+
smaliQuotedStringParser,
|
|
713
|
+
value => ({ kind: 'string' as const, value }),
|
|
714
|
+
),
|
|
715
|
+
promiseCompose(
|
|
716
|
+
smaliParametersMethodParser,
|
|
717
|
+
value => ({ kind: 'raw' as const, value }),
|
|
718
|
+
),
|
|
719
|
+
promiseCompose(
|
|
720
|
+
smaliTypeDescriptorParser,
|
|
721
|
+
value => ({ kind: 'type' as const, value }),
|
|
722
|
+
),
|
|
723
|
+
promiseCompose(
|
|
724
|
+
smaliNumberParser,
|
|
725
|
+
value => ({ kind: 'raw' as const, value }),
|
|
726
|
+
),
|
|
727
|
+
promiseCompose(
|
|
728
|
+
createExactSequenceParser('true'),
|
|
729
|
+
() => ({ kind: 'raw' as const, value: true }),
|
|
730
|
+
),
|
|
731
|
+
promiseCompose(
|
|
732
|
+
createExactSequenceParser('false'),
|
|
733
|
+
() => ({ kind: 'raw' as const, value: false }),
|
|
734
|
+
),
|
|
735
|
+
promiseCompose(
|
|
736
|
+
createExactSequenceParser('null'),
|
|
737
|
+
() => ({ kind: 'raw' as const, value: null }),
|
|
738
|
+
),
|
|
739
|
+
promiseCompose(
|
|
740
|
+
createExactSequenceParser('{}'),
|
|
741
|
+
() => ({ kind: 'raw' as const, value: [] }),
|
|
742
|
+
),
|
|
743
|
+
promiseCompose(
|
|
744
|
+
createTupleParser([
|
|
745
|
+
createExactSequenceParser('{\n'),
|
|
746
|
+
createSeparatedArrayParser(
|
|
747
|
+
promiseCompose(
|
|
748
|
+
createTupleParser([
|
|
749
|
+
smaliIndentationParser,
|
|
750
|
+
smaliEnumValueParser,
|
|
751
|
+
]),
|
|
752
|
+
([
|
|
753
|
+
_indentation,
|
|
754
|
+
value,
|
|
755
|
+
]) => value,
|
|
756
|
+
),
|
|
757
|
+
createExactSequenceParser(',\n'),
|
|
758
|
+
),
|
|
759
|
+
smaliLineEndPraser,
|
|
760
|
+
smaliIndentationParser,
|
|
761
|
+
createExactSequenceParser('}'),
|
|
762
|
+
]),
|
|
763
|
+
([
|
|
764
|
+
_openBrace,
|
|
765
|
+
value,
|
|
766
|
+
_closeBrace,
|
|
767
|
+
]) => ({ kind: 'enum' as const, value }),
|
|
768
|
+
),
|
|
769
|
+
promiseCompose(
|
|
770
|
+
createTupleParser([
|
|
771
|
+
createExactSequenceParser('{\n'),
|
|
772
|
+
createSeparatedArrayParser(
|
|
773
|
+
promiseCompose(
|
|
774
|
+
createTupleParser([
|
|
775
|
+
smaliIndentationParser,
|
|
776
|
+
smaliTypeDescriptorParser,
|
|
777
|
+
]),
|
|
778
|
+
([
|
|
779
|
+
_indentation,
|
|
780
|
+
value,
|
|
781
|
+
]) => value,
|
|
782
|
+
),
|
|
783
|
+
createExactSequenceParser(',\n'),
|
|
784
|
+
),
|
|
785
|
+
smaliLineEndPraser,
|
|
786
|
+
smaliIndentationParser,
|
|
787
|
+
createExactSequenceParser('}'),
|
|
788
|
+
]),
|
|
789
|
+
([
|
|
790
|
+
_openBrace,
|
|
791
|
+
value,
|
|
792
|
+
_closeBrace,
|
|
793
|
+
]) => ({ kind: 'type' as const, value }),
|
|
794
|
+
),
|
|
795
|
+
promiseCompose(
|
|
796
|
+
createTupleParser([
|
|
797
|
+
createExactSequenceParser('{\n'),
|
|
798
|
+
createSeparatedArrayParser(
|
|
799
|
+
promiseCompose(
|
|
800
|
+
createTupleParser([
|
|
801
|
+
smaliIndentationParser,
|
|
802
|
+
smaliQuotedStringParser,
|
|
803
|
+
]),
|
|
804
|
+
([
|
|
805
|
+
_indentation,
|
|
806
|
+
value,
|
|
807
|
+
]) => value,
|
|
808
|
+
),
|
|
809
|
+
createExactSequenceParser(',\n'),
|
|
810
|
+
),
|
|
811
|
+
smaliLineEndPraser,
|
|
812
|
+
smaliIndentationParser,
|
|
813
|
+
createExactSequenceParser('}'),
|
|
814
|
+
]),
|
|
815
|
+
([
|
|
816
|
+
_openBrace,
|
|
817
|
+
value,
|
|
818
|
+
_closeBrace,
|
|
819
|
+
]) => ({ kind: 'string' as const, value }),
|
|
820
|
+
),
|
|
821
|
+
promiseCompose(
|
|
822
|
+
createTupleParser([
|
|
823
|
+
createExactSequenceParser('{\n'),
|
|
824
|
+
createSeparatedArrayParser(
|
|
825
|
+
promiseCompose(
|
|
826
|
+
createTupleParser([
|
|
827
|
+
smaliIndentationParser,
|
|
828
|
+
smaliNumberParser,
|
|
829
|
+
]),
|
|
830
|
+
([
|
|
831
|
+
_indentation,
|
|
832
|
+
value,
|
|
833
|
+
]) => value,
|
|
834
|
+
),
|
|
835
|
+
createExactSequenceParser(',\n'),
|
|
836
|
+
),
|
|
837
|
+
smaliLineEndPraser,
|
|
838
|
+
smaliIndentationParser,
|
|
839
|
+
createExactSequenceParser('}'),
|
|
840
|
+
]),
|
|
841
|
+
([
|
|
842
|
+
_openBrace,
|
|
843
|
+
value,
|
|
844
|
+
_closeBrace,
|
|
845
|
+
]) => ({ kind: 'raw' as const, value }),
|
|
846
|
+
),
|
|
847
|
+
]),
|
|
848
|
+
smaliLineEndPraser,
|
|
849
|
+
]),
|
|
850
|
+
([
|
|
851
|
+
_indentation,
|
|
852
|
+
name,
|
|
853
|
+
_equalsSign,
|
|
854
|
+
value,
|
|
855
|
+
_newline,
|
|
856
|
+
]): SmaliAnnotationElement => ({
|
|
857
|
+
name,
|
|
858
|
+
value: value as SmaliAnnotationElementValue,
|
|
859
|
+
}),
|
|
860
|
+
);
|
|
861
|
+
|
|
862
|
+
setParserName(smaliAnnotationElementParser, 'smaliAnnotationElementParser');
|
|
863
|
+
|
|
864
|
+
// Now define the subannotation parser
|
|
865
|
+
smaliSubannotationParser = promiseCompose(
|
|
866
|
+
createTupleParser([
|
|
867
|
+
createExactSequenceParser('.subannotation '),
|
|
868
|
+
smaliTypeDescriptorParser,
|
|
869
|
+
smaliLineEndPraser,
|
|
870
|
+
createArrayParser(smaliAnnotationElementParser),
|
|
871
|
+
smaliIndentationParser,
|
|
872
|
+
createExactSequenceParser('.end subannotation'),
|
|
873
|
+
]),
|
|
874
|
+
([
|
|
875
|
+
_subannotation,
|
|
876
|
+
type,
|
|
877
|
+
_newline,
|
|
878
|
+
elements,
|
|
879
|
+
_indentation,
|
|
880
|
+
_endSubannotation,
|
|
881
|
+
]) => ({
|
|
882
|
+
type,
|
|
883
|
+
elements,
|
|
884
|
+
}),
|
|
885
|
+
);
|
|
886
|
+
|
|
887
|
+
setParserName(smaliSubannotationParser, 'smaliSubannotationParser');
|
|
888
|
+
|
|
889
|
+
type SmaliAnnotation = {
|
|
890
|
+
type: string;
|
|
891
|
+
elements: SmaliAnnotationElement[];
|
|
892
|
+
visibility: 'build' | 'runtime' | 'system';
|
|
893
|
+
};
|
|
894
|
+
|
|
895
|
+
export const smaliAnnotationParser: Parser<SmaliAnnotation, string> = promiseCompose(
|
|
896
|
+
createTupleParser([
|
|
897
|
+
smaliIndentationParser,
|
|
898
|
+
createExactSequenceParser('.annotation '),
|
|
899
|
+
createUnionParser<'build' | 'runtime' | 'system', string, string>([
|
|
900
|
+
createExactSequenceParser('build'),
|
|
901
|
+
createExactSequenceParser('runtime'),
|
|
902
|
+
createExactSequenceParser('system'),
|
|
903
|
+
]),
|
|
904
|
+
smaliSingleWhitespaceParser,
|
|
905
|
+
smaliTypeDescriptorParser,
|
|
906
|
+
smaliLineEndPraser,
|
|
907
|
+
createArrayParser(smaliAnnotationElementParser),
|
|
908
|
+
smaliIndentationParser,
|
|
909
|
+
createExactSequenceParser('.end annotation\n'),
|
|
910
|
+
]),
|
|
911
|
+
([
|
|
912
|
+
_indentation0,
|
|
913
|
+
_annotation,
|
|
914
|
+
visibility,
|
|
915
|
+
_space,
|
|
916
|
+
type,
|
|
917
|
+
_newline,
|
|
918
|
+
elements,
|
|
919
|
+
_indentation1,
|
|
920
|
+
_endAnnotation,
|
|
921
|
+
]) => ({
|
|
922
|
+
type,
|
|
923
|
+
elements,
|
|
924
|
+
visibility,
|
|
925
|
+
}),
|
|
926
|
+
);
|
|
927
|
+
|
|
928
|
+
setParserName(smaliAnnotationParser, 'smaliAnnotationParser');
|
|
929
|
+
|
|
930
|
+
type SmaliField = {
|
|
931
|
+
field: DalvikExecutableFieldWithAccess;
|
|
932
|
+
annotations: SmaliAnnotation[];
|
|
933
|
+
initialValue?: number | bigint | string | boolean | null;
|
|
934
|
+
};
|
|
935
|
+
|
|
936
|
+
export const smaliFieldParser: Parser<SmaliField, string> = promiseCompose(
|
|
937
|
+
createTupleParser([
|
|
938
|
+
createExactSequenceParser('.field '),
|
|
939
|
+
createOptionalParser(promiseCompose(
|
|
940
|
+
createTupleParser([
|
|
941
|
+
smaliAccessFlagsParser,
|
|
942
|
+
smaliSingleWhitespaceParser,
|
|
943
|
+
]),
|
|
944
|
+
([accessFlags, _space]) => accessFlags,
|
|
945
|
+
)),
|
|
946
|
+
smaliMemberNameParser,
|
|
947
|
+
createExactSequenceParser(':'),
|
|
948
|
+
smaliTypeDescriptorParser,
|
|
949
|
+
createOptionalParser(promiseCompose(
|
|
950
|
+
createTupleParser([
|
|
951
|
+
createExactSequenceParser(' = '),
|
|
952
|
+
createUnionParser<number | bigint | string | boolean | null, string>([
|
|
953
|
+
smaliCharacterLiteralParser,
|
|
954
|
+
smaliFieldValueParser,
|
|
955
|
+
smaliQuotedStringParser,
|
|
956
|
+
promiseCompose(
|
|
957
|
+
createExactSequenceParser('true'),
|
|
958
|
+
() => true,
|
|
959
|
+
),
|
|
960
|
+
promiseCompose(
|
|
961
|
+
createExactSequenceParser('false'),
|
|
962
|
+
() => false,
|
|
963
|
+
),
|
|
964
|
+
promiseCompose(
|
|
965
|
+
createExactSequenceParser('null'),
|
|
966
|
+
() => null,
|
|
967
|
+
),
|
|
968
|
+
]),
|
|
969
|
+
]),
|
|
970
|
+
([
|
|
971
|
+
_equals,
|
|
972
|
+
value,
|
|
973
|
+
]) => value,
|
|
974
|
+
)),
|
|
975
|
+
smaliLineEndPraser,
|
|
976
|
+
createOptionalParser(promiseCompose(
|
|
977
|
+
createTupleParser([
|
|
978
|
+
createSeparatedArrayParser(
|
|
979
|
+
smaliAnnotationParser,
|
|
980
|
+
smaliNewlinesParser,
|
|
981
|
+
),
|
|
982
|
+
createExactSequenceParser('.end field\n'),
|
|
983
|
+
]),
|
|
984
|
+
([
|
|
985
|
+
annotations,
|
|
986
|
+
_endField,
|
|
987
|
+
]) => annotations,
|
|
988
|
+
)),
|
|
989
|
+
]),
|
|
990
|
+
([
|
|
991
|
+
_field,
|
|
992
|
+
accessFlags,
|
|
993
|
+
name,
|
|
994
|
+
_colon,
|
|
995
|
+
type,
|
|
996
|
+
initialValue,
|
|
997
|
+
_newline,
|
|
998
|
+
annotations,
|
|
999
|
+
]) => ({
|
|
1000
|
+
field: {
|
|
1001
|
+
accessFlags: accessFlags ?? dalvikExecutableAccessFlagsDefault(),
|
|
1002
|
+
field: {
|
|
1003
|
+
class: 'FILLED_LATER',
|
|
1004
|
+
type,
|
|
1005
|
+
name,
|
|
1006
|
+
},
|
|
1007
|
+
},
|
|
1008
|
+
annotations: annotations ?? [],
|
|
1009
|
+
...(initialValue !== undefined ? { initialValue } : {}),
|
|
1010
|
+
}),
|
|
1011
|
+
);
|
|
1012
|
+
|
|
1013
|
+
setParserName(smaliFieldParser, 'smaliFieldParser');
|
|
1014
|
+
|
|
1015
|
+
type SmaliFields = {
|
|
1016
|
+
staticFields: SmaliField[];
|
|
1017
|
+
instanceFields: SmaliField[];
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
const smaliFieldsParser: Parser<SmaliFields, string> = promiseCompose(
|
|
1021
|
+
createSeparatedNonEmptyArrayParser<string[] | SmaliField, string>(
|
|
1022
|
+
smaliFieldParser,
|
|
1023
|
+
smaliCommentsOrNewlinesParser,
|
|
1024
|
+
),
|
|
1025
|
+
fieldsAndComments => {
|
|
1026
|
+
let type: 'staticField' | 'instanceField' = 'instanceField';
|
|
1027
|
+
|
|
1028
|
+
const staticFields: SmaliField[] = [];
|
|
1029
|
+
const instanceFields: SmaliField[] = [];
|
|
1030
|
+
|
|
1031
|
+
for (const fieldOrComment of fieldsAndComments) {
|
|
1032
|
+
if (Array.isArray(fieldOrComment)) {
|
|
1033
|
+
for (const comment of fieldOrComment) {
|
|
1034
|
+
if (comment === ' static fields') {
|
|
1035
|
+
type = 'staticField';
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
if (comment === ' instance fields') {
|
|
1039
|
+
type = 'instanceField';
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
invariant(typeof fieldOrComment === 'object', 'Expected field or comment');
|
|
1047
|
+
|
|
1048
|
+
const field = fieldOrComment;
|
|
1049
|
+
|
|
1050
|
+
if (
|
|
1051
|
+
type === 'staticField'
|
|
1052
|
+
|| field.field.accessFlags.static
|
|
1053
|
+
) {
|
|
1054
|
+
staticFields.push(field);
|
|
1055
|
+
|
|
1056
|
+
continue;
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
if (type === 'instanceField') {
|
|
1060
|
+
instanceFields.push(field);
|
|
1061
|
+
|
|
1062
|
+
continue;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
invariant(false, 'Expected field type');
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
return {
|
|
1069
|
+
staticFields,
|
|
1070
|
+
instanceFields,
|
|
1071
|
+
};
|
|
1072
|
+
},
|
|
1073
|
+
);
|
|
1074
|
+
|
|
1075
|
+
setParserName(smaliFieldsParser, 'smaliFieldsParser');
|
|
1076
|
+
|
|
1077
|
+
const smaliShortyFieldTypeParser: Parser<string, string> = createUnionParser([
|
|
1078
|
+
createExactSequenceParser('Z'),
|
|
1079
|
+
createExactSequenceParser('B'),
|
|
1080
|
+
createExactSequenceParser('S'),
|
|
1081
|
+
createExactSequenceParser('C'),
|
|
1082
|
+
createExactSequenceParser('I'),
|
|
1083
|
+
createExactSequenceParser('J'),
|
|
1084
|
+
createExactSequenceParser('F'),
|
|
1085
|
+
createExactSequenceParser('D'),
|
|
1086
|
+
]);
|
|
1087
|
+
|
|
1088
|
+
setParserName(smaliShortyFieldTypeParser, 'smaliShortyFieldTypeParser');
|
|
1089
|
+
|
|
1090
|
+
const smaliShortyReturnTypeParser: Parser<string, string> = createUnionParser([
|
|
1091
|
+
createExactSequenceParser('V'),
|
|
1092
|
+
smaliShortyFieldTypeParser,
|
|
1093
|
+
]);
|
|
1094
|
+
|
|
1095
|
+
setParserName(smaliShortyReturnTypeParser, 'smaliShortyReturnTypeParser');
|
|
1096
|
+
|
|
1097
|
+
function shortyGetInsSize(shorty: string): number {
|
|
1098
|
+
let size = 0;
|
|
1099
|
+
|
|
1100
|
+
for (const char of shorty.slice(1)) {
|
|
1101
|
+
if (char === 'J' || char === 'D') {
|
|
1102
|
+
size += 2;
|
|
1103
|
+
} else if (char !== 'V') {
|
|
1104
|
+
size += 1;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
return size;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const smaliCodeRegistersParser: Parser<number, string> = promiseCompose(
|
|
1112
|
+
createTupleParser([
|
|
1113
|
+
createExactSequenceParser(' .registers '),
|
|
1114
|
+
smaliNumberParser,
|
|
1115
|
+
smaliLineEndPraser,
|
|
1116
|
+
]),
|
|
1117
|
+
([
|
|
1118
|
+
_registers,
|
|
1119
|
+
registers,
|
|
1120
|
+
_newline,
|
|
1121
|
+
]) => registers,
|
|
1122
|
+
);
|
|
1123
|
+
|
|
1124
|
+
setParserName(smaliCodeRegistersParser, 'smaliCodeRegistersParser');
|
|
1125
|
+
|
|
1126
|
+
const smaliCodeLineParser: Parser<number, string> = promiseCompose(
|
|
1127
|
+
createTupleParser([
|
|
1128
|
+
createExactSequenceParser(' .line '),
|
|
1129
|
+
smaliNumberParser,
|
|
1130
|
+
smaliLineEndPraser,
|
|
1131
|
+
]),
|
|
1132
|
+
([
|
|
1133
|
+
_line,
|
|
1134
|
+
line,
|
|
1135
|
+
_newline,
|
|
1136
|
+
]) => line,
|
|
1137
|
+
);
|
|
1138
|
+
|
|
1139
|
+
setParserName(smaliCodeLineParser, 'smaliCodeLineParser');
|
|
1140
|
+
|
|
1141
|
+
type SmaliRegister = {
|
|
1142
|
+
prefix: 'v' | 'p';
|
|
1143
|
+
index: number;
|
|
1144
|
+
};
|
|
1145
|
+
|
|
1146
|
+
function isSmaliRegister(value: unknown): value is SmaliRegister {
|
|
1147
|
+
return (
|
|
1148
|
+
value !== null
|
|
1149
|
+
&& typeof value === 'object'
|
|
1150
|
+
&& 'prefix' in value
|
|
1151
|
+
&& 'index' in value
|
|
1152
|
+
&& typeof (value as SmaliRegister).prefix === 'string'
|
|
1153
|
+
&& typeof (value as SmaliRegister).index === 'number'
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
const smaliParametersRegisterParser: Parser<SmaliRegister, string> = promiseCompose(
|
|
1158
|
+
createUnionParser<['v' | 'p', number], string>([
|
|
1159
|
+
createTupleParser([
|
|
1160
|
+
createExactSequenceParser('v'),
|
|
1161
|
+
smaliNumberParser,
|
|
1162
|
+
]),
|
|
1163
|
+
createTupleParser([
|
|
1164
|
+
createExactSequenceParser('p'),
|
|
1165
|
+
smaliNumberParser,
|
|
1166
|
+
]),
|
|
1167
|
+
]),
|
|
1168
|
+
([
|
|
1169
|
+
prefix,
|
|
1170
|
+
index,
|
|
1171
|
+
]) => ({
|
|
1172
|
+
prefix,
|
|
1173
|
+
index,
|
|
1174
|
+
}),
|
|
1175
|
+
);
|
|
1176
|
+
|
|
1177
|
+
setParserName(smaliParametersRegisterParser, 'smaliParametersRegisterParser');
|
|
1178
|
+
|
|
1179
|
+
type SmaliCodeLocal = {
|
|
1180
|
+
register: SmaliRegister;
|
|
1181
|
+
name: string | undefined;
|
|
1182
|
+
type: string | undefined;
|
|
1183
|
+
};
|
|
1184
|
+
|
|
1185
|
+
const smaliCodeLocalParser: Parser<SmaliCodeLocal, string> = promiseCompose(
|
|
1186
|
+
createTupleParser([
|
|
1187
|
+
createExactSequenceParser(' .local '),
|
|
1188
|
+
smaliParametersRegisterParser,
|
|
1189
|
+
createOptionalParser(createTupleParser([
|
|
1190
|
+
createExactSequenceParser(','),
|
|
1191
|
+
smaliWhitespaceParser,
|
|
1192
|
+
smaliQuotedStringParser,
|
|
1193
|
+
createExactSequenceParser(':'),
|
|
1194
|
+
smaliTypeDescriptorParser,
|
|
1195
|
+
])),
|
|
1196
|
+
smaliLineEndPraser,
|
|
1197
|
+
]),
|
|
1198
|
+
([
|
|
1199
|
+
_local,
|
|
1200
|
+
register,
|
|
1201
|
+
nameAndType,
|
|
1202
|
+
]) => ({
|
|
1203
|
+
register,
|
|
1204
|
+
name: nameAndType?.[2],
|
|
1205
|
+
type: nameAndType?.[4],
|
|
1206
|
+
}),
|
|
1207
|
+
);
|
|
1208
|
+
|
|
1209
|
+
setParserName(smaliCodeLocalParser, 'smaliCodeLocalParser');
|
|
1210
|
+
|
|
1211
|
+
const smaliCodeEndLocalParser: Parser<SmaliRegister, string> = promiseCompose(
|
|
1212
|
+
createTupleParser([
|
|
1213
|
+
createExactSequenceParser(' .end local '),
|
|
1214
|
+
smaliParametersRegisterParser,
|
|
1215
|
+
smaliLineEndPraser,
|
|
1216
|
+
]),
|
|
1217
|
+
([
|
|
1218
|
+
_endLocal,
|
|
1219
|
+
register,
|
|
1220
|
+
_newline,
|
|
1221
|
+
]) => register,
|
|
1222
|
+
);
|
|
1223
|
+
|
|
1224
|
+
setParserName(smaliCodeEndLocalParser, 'smaliCodeEndLocalParser');
|
|
1225
|
+
|
|
1226
|
+
const smaliCodeRestartLocalParser: Parser<SmaliRegister, string> = promiseCompose(
|
|
1227
|
+
createTupleParser([
|
|
1228
|
+
createExactSequenceParser(' .restart local '),
|
|
1229
|
+
smaliParametersRegisterParser,
|
|
1230
|
+
smaliLineEndPraser,
|
|
1231
|
+
]),
|
|
1232
|
+
([
|
|
1233
|
+
_restartLocal,
|
|
1234
|
+
register,
|
|
1235
|
+
_newline,
|
|
1236
|
+
]) => register,
|
|
1237
|
+
);
|
|
1238
|
+
|
|
1239
|
+
setParserName(smaliCodeRestartLocalParser, 'smaliCodeRestartLocalParser');
|
|
1240
|
+
|
|
1241
|
+
const smaliCodePrologueEndParser: Parser<true, string> = promiseCompose(
|
|
1242
|
+
createTupleParser([
|
|
1243
|
+
createExactSequenceParser(' .prologue'),
|
|
1244
|
+
smaliLineEndPraser,
|
|
1245
|
+
]),
|
|
1246
|
+
() => true as const,
|
|
1247
|
+
);
|
|
1248
|
+
|
|
1249
|
+
setParserName(smaliCodePrologueEndParser, 'smaliCodePrologueEndParser');
|
|
1250
|
+
|
|
1251
|
+
const smaliCodeEpilogueBeginParser: Parser<true, string> = promiseCompose(
|
|
1252
|
+
createTupleParser([
|
|
1253
|
+
createExactSequenceParser(' .epilogue'),
|
|
1254
|
+
smaliLineEndPraser,
|
|
1255
|
+
]),
|
|
1256
|
+
() => true as const,
|
|
1257
|
+
);
|
|
1258
|
+
|
|
1259
|
+
setParserName(smaliCodeEpilogueBeginParser, 'smaliCodeEpilogueBeginParser');
|
|
1260
|
+
|
|
1261
|
+
type SmaliCodeParameter = {
|
|
1262
|
+
register: SmaliRegister;
|
|
1263
|
+
name: string | undefined;
|
|
1264
|
+
annotation: SmaliAnnotation | undefined;
|
|
1265
|
+
};
|
|
1266
|
+
|
|
1267
|
+
export const smaliCodeParameterParser: Parser<SmaliCodeParameter, string> = promiseCompose(
|
|
1268
|
+
createTupleParser([
|
|
1269
|
+
createExactSequenceParser(' .param '),
|
|
1270
|
+
smaliParametersRegisterParser,
|
|
1271
|
+
createOptionalParser(createTupleParser([
|
|
1272
|
+
createExactSequenceParser(','),
|
|
1273
|
+
smaliWhitespaceParser,
|
|
1274
|
+
smaliQuotedStringParser,
|
|
1275
|
+
smaliWhitespaceParser,
|
|
1276
|
+
])),
|
|
1277
|
+
createOptionalParser(smaliWhitespaceParser),
|
|
1278
|
+
smaliCommentsOrNewlinesParser,
|
|
1279
|
+
createOptionalParser(createTupleParser([
|
|
1280
|
+
smaliAnnotationParser,
|
|
1281
|
+
smaliIndentationParser,
|
|
1282
|
+
createExactSequenceParser('.end param\n'),
|
|
1283
|
+
])),
|
|
1284
|
+
]),
|
|
1285
|
+
([
|
|
1286
|
+
_parameter,
|
|
1287
|
+
register,
|
|
1288
|
+
optionalCommaAndString,
|
|
1289
|
+
_whitespace,
|
|
1290
|
+
_commentOrNewline,
|
|
1291
|
+
optionalAnnotation,
|
|
1292
|
+
]) => {
|
|
1293
|
+
let annotation: undefined | SmaliAnnotation;
|
|
1294
|
+
|
|
1295
|
+
if (optionalAnnotation) {
|
|
1296
|
+
annotation = optionalAnnotation[0];
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
return {
|
|
1300
|
+
register,
|
|
1301
|
+
name: optionalCommaAndString?.[2],
|
|
1302
|
+
annotation,
|
|
1303
|
+
};
|
|
1304
|
+
},
|
|
1305
|
+
);
|
|
1306
|
+
|
|
1307
|
+
setParserName(smaliCodeParameterParser, 'smaliCodeParameterParser');
|
|
1308
|
+
|
|
1309
|
+
const smaliCodeLabelParser: Parser<string, string> = promiseCompose(
|
|
1310
|
+
createTupleParser([
|
|
1311
|
+
createExactSequenceParser(':'),
|
|
1312
|
+
smaliIdentifierParser,
|
|
1313
|
+
]),
|
|
1314
|
+
([
|
|
1315
|
+
_label,
|
|
1316
|
+
label,
|
|
1317
|
+
]) => label,
|
|
1318
|
+
);
|
|
1319
|
+
|
|
1320
|
+
setParserName(smaliCodeLabelParser, 'smaliCodeLabelParser');
|
|
1321
|
+
|
|
1322
|
+
const smaliCodeLabelLineParser: Parser<string, string> = promiseCompose(
|
|
1323
|
+
createTupleParser([
|
|
1324
|
+
smaliIndentationParser,
|
|
1325
|
+
smaliCodeLabelParser,
|
|
1326
|
+
smaliLineEndPraser,
|
|
1327
|
+
]),
|
|
1328
|
+
([
|
|
1329
|
+
_label,
|
|
1330
|
+
label,
|
|
1331
|
+
_newlabel,
|
|
1332
|
+
]) => label,
|
|
1333
|
+
);
|
|
1334
|
+
|
|
1335
|
+
setParserName(smaliCodeLabelLineParser, 'smaliCodeLabelLineParser');
|
|
1336
|
+
|
|
1337
|
+
type SmaliCatchDirective = {
|
|
1338
|
+
type: string | undefined; // undefined for .catchall
|
|
1339
|
+
startLabel: string;
|
|
1340
|
+
endLabel: string;
|
|
1341
|
+
handlerLabel: string;
|
|
1342
|
+
};
|
|
1343
|
+
|
|
1344
|
+
const smaliCatchDirectiveParser: Parser<SmaliCatchDirective, string> = promiseCompose(
|
|
1345
|
+
createTupleParser([
|
|
1346
|
+
smaliIndentationParser,
|
|
1347
|
+
createExactSequenceParser('.catch'),
|
|
1348
|
+
createUnionParser<string | undefined, string, string>([
|
|
1349
|
+
promiseCompose(
|
|
1350
|
+
createExactSequenceParser('all'),
|
|
1351
|
+
() => undefined as undefined,
|
|
1352
|
+
),
|
|
1353
|
+
promiseCompose(
|
|
1354
|
+
createTupleParser([
|
|
1355
|
+
createExactSequenceParser(' '),
|
|
1356
|
+
smaliTypeDescriptorParser,
|
|
1357
|
+
]),
|
|
1358
|
+
([
|
|
1359
|
+
_space,
|
|
1360
|
+
type,
|
|
1361
|
+
]) => type,
|
|
1362
|
+
),
|
|
1363
|
+
]),
|
|
1364
|
+
createExactSequenceParser(' {'),
|
|
1365
|
+
smaliCodeLabelParser,
|
|
1366
|
+
createExactSequenceParser(' .. '),
|
|
1367
|
+
smaliCodeLabelParser,
|
|
1368
|
+
createExactSequenceParser('} '),
|
|
1369
|
+
smaliCodeLabelParser,
|
|
1370
|
+
smaliLineEndPraser,
|
|
1371
|
+
]),
|
|
1372
|
+
([
|
|
1373
|
+
_indentation,
|
|
1374
|
+
_catch,
|
|
1375
|
+
type,
|
|
1376
|
+
_openBrace,
|
|
1377
|
+
startLabel,
|
|
1378
|
+
_dots,
|
|
1379
|
+
endLabel,
|
|
1380
|
+
_closeBrace,
|
|
1381
|
+
handlerLabel,
|
|
1382
|
+
_newline,
|
|
1383
|
+
]) => ({
|
|
1384
|
+
type,
|
|
1385
|
+
startLabel,
|
|
1386
|
+
endLabel,
|
|
1387
|
+
handlerLabel,
|
|
1388
|
+
}),
|
|
1389
|
+
);
|
|
1390
|
+
|
|
1391
|
+
setParserName(smaliCatchDirectiveParser, 'smaliCatchDirectiveParser');
|
|
1392
|
+
|
|
1393
|
+
type SmaliLabeledCatchDirective = {
|
|
1394
|
+
labels: string[];
|
|
1395
|
+
catchDirective: SmaliCatchDirective;
|
|
1396
|
+
};
|
|
1397
|
+
|
|
1398
|
+
const smaliLabeledCatchDirectiveParser: Parser<SmaliLabeledCatchDirective, string> = promiseCompose(
|
|
1399
|
+
createTupleParser([
|
|
1400
|
+
createArrayParser(smaliCodeLineParser),
|
|
1401
|
+
createOptionalParser(smaliCodeLocalParser),
|
|
1402
|
+
createArrayParser(smaliCodeLabelLineParser),
|
|
1403
|
+
smaliCatchDirectiveParser,
|
|
1404
|
+
]),
|
|
1405
|
+
([
|
|
1406
|
+
_lines,
|
|
1407
|
+
_local,
|
|
1408
|
+
labels,
|
|
1409
|
+
catchDirective,
|
|
1410
|
+
]) => ({
|
|
1411
|
+
labels,
|
|
1412
|
+
catchDirective,
|
|
1413
|
+
}),
|
|
1414
|
+
);
|
|
1415
|
+
|
|
1416
|
+
setParserName(smaliLabeledCatchDirectiveParser, 'smaliLabeledCatchDirectiveParser');
|
|
1417
|
+
|
|
1418
|
+
const smaliParametersRegisterRangeParser: Parser<SmaliRegister[], string> = promiseCompose(
|
|
1419
|
+
createTupleParser([
|
|
1420
|
+
createExactSequenceParser('{'),
|
|
1421
|
+
smaliParametersRegisterParser,
|
|
1422
|
+
createExactSequenceParser(' .. '),
|
|
1423
|
+
smaliParametersRegisterParser,
|
|
1424
|
+
createExactSequenceParser('}'),
|
|
1425
|
+
]),
|
|
1426
|
+
([
|
|
1427
|
+
_openBrace,
|
|
1428
|
+
startRegister,
|
|
1429
|
+
_dotDot,
|
|
1430
|
+
endRegister,
|
|
1431
|
+
_closeBrace,
|
|
1432
|
+
]) => {
|
|
1433
|
+
invariant(
|
|
1434
|
+
startRegister.prefix === endRegister.prefix,
|
|
1435
|
+
'Register range must use the same prefix',
|
|
1436
|
+
);
|
|
1437
|
+
|
|
1438
|
+
invariant(
|
|
1439
|
+
startRegister.index <= endRegister.index,
|
|
1440
|
+
'Register range start must be less than or equal to end',
|
|
1441
|
+
);
|
|
1442
|
+
|
|
1443
|
+
const registers: SmaliRegister[] = [];
|
|
1444
|
+
for (let i = startRegister.index; i <= endRegister.index; i++) {
|
|
1445
|
+
registers.push({
|
|
1446
|
+
prefix: startRegister.prefix,
|
|
1447
|
+
index: i,
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
return registers;
|
|
1452
|
+
},
|
|
1453
|
+
);
|
|
1454
|
+
|
|
1455
|
+
setParserName(smaliParametersRegisterRangeParser, 'smaliParametersRegisterRangeParser');
|
|
1456
|
+
|
|
1457
|
+
const smaliParametersRegisterListParser: Parser<SmaliRegister[], string> = promiseCompose(
|
|
1458
|
+
createTupleParser([
|
|
1459
|
+
createExactSequenceParser('{'),
|
|
1460
|
+
createArrayParser(promiseCompose(
|
|
1461
|
+
createTupleParser([
|
|
1462
|
+
smaliParametersRegisterParser,
|
|
1463
|
+
createOptionalParser(createExactSequenceParser(', ')),
|
|
1464
|
+
]),
|
|
1465
|
+
([
|
|
1466
|
+
parameter,
|
|
1467
|
+
_comma,
|
|
1468
|
+
]) => parameter,
|
|
1469
|
+
)),
|
|
1470
|
+
createExactSequenceParser('}'),
|
|
1471
|
+
]),
|
|
1472
|
+
([
|
|
1473
|
+
_openBrace,
|
|
1474
|
+
parameters,
|
|
1475
|
+
_closeBrace,
|
|
1476
|
+
]) => parameters,
|
|
1477
|
+
);
|
|
1478
|
+
|
|
1479
|
+
setParserName(smaliParametersRegisterListParser, 'smaliParametersRegisterListParser');
|
|
1480
|
+
|
|
1481
|
+
const smaliParametersRegistersParser: Parser<SmaliRegister[], string> = createUnionParser([
|
|
1482
|
+
smaliParametersRegisterRangeParser,
|
|
1483
|
+
smaliParametersRegisterListParser,
|
|
1484
|
+
]);
|
|
1485
|
+
|
|
1486
|
+
setParserName(smaliParametersRegistersParser, 'smaliParametersRegistersParser');
|
|
1487
|
+
|
|
1488
|
+
const smaliParametersStringParser: Parser<string, string> = cloneParser(smaliQuotedStringParser);
|
|
1489
|
+
|
|
1490
|
+
setParserName(smaliParametersStringParser, 'smaliParametersStringParser');
|
|
1491
|
+
|
|
1492
|
+
const smaliParametersIntegerParser: Parser<number | bigint, string> = promiseCompose(
|
|
1493
|
+
createTupleParser([
|
|
1494
|
+
createOptionalParser(createExactSequenceParser('-')),
|
|
1495
|
+
createExactSequenceParser('0x'),
|
|
1496
|
+
async parserContext => {
|
|
1497
|
+
const characters: string[] = [];
|
|
1498
|
+
|
|
1499
|
+
while (true) {
|
|
1500
|
+
const character = await parserContext.peek(0);
|
|
1501
|
+
|
|
1502
|
+
parserContext.invariant(character !== undefined, 'Unexpected end of input');
|
|
1503
|
+
|
|
1504
|
+
invariant(character !== undefined, 'Unexpected end of input');
|
|
1505
|
+
|
|
1506
|
+
if (
|
|
1507
|
+
(character >= '0' && character <= '9')
|
|
1508
|
+
|| (character >= 'a' && character <= 'f')
|
|
1509
|
+
|| (character >= 'A' && character <= 'F')
|
|
1510
|
+
) {
|
|
1511
|
+
characters.push(character);
|
|
1512
|
+
|
|
1513
|
+
parserContext.skip(1);
|
|
1514
|
+
|
|
1515
|
+
continue;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
break;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
return characters.join('');
|
|
1522
|
+
},
|
|
1523
|
+
createOptionalParser(createUnionParser([
|
|
1524
|
+
createExactSequenceParser('L'),
|
|
1525
|
+
createExactSequenceParser('t'),
|
|
1526
|
+
createExactSequenceParser('s'),
|
|
1527
|
+
])),
|
|
1528
|
+
]),
|
|
1529
|
+
([
|
|
1530
|
+
optionalMinus,
|
|
1531
|
+
_0x,
|
|
1532
|
+
value,
|
|
1533
|
+
optionalSuffix,
|
|
1534
|
+
]) => {
|
|
1535
|
+
if (optionalSuffix === 'L') {
|
|
1536
|
+
const sign = optionalMinus ? -1n : 1n;
|
|
1537
|
+
|
|
1538
|
+
return sign * BigInt('0x' + value);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
const sign = optionalMinus ? -1 : 1;
|
|
1542
|
+
|
|
1543
|
+
return sign * Number.parseInt(value, 16);
|
|
1544
|
+
},
|
|
1545
|
+
);
|
|
1546
|
+
|
|
1547
|
+
setParserName(smaliParametersIntegerParser, 'smaliParametersIntegerParser');
|
|
1548
|
+
|
|
1549
|
+
const smaliParametersLabelParser: Parser<string, string> = promiseCompose(
|
|
1550
|
+
createTupleParser([
|
|
1551
|
+
createExactSequenceParser(':'),
|
|
1552
|
+
smaliIdentifierParser,
|
|
1553
|
+
]),
|
|
1554
|
+
([
|
|
1555
|
+
_label,
|
|
1556
|
+
label,
|
|
1557
|
+
]) => label,
|
|
1558
|
+
);
|
|
1559
|
+
|
|
1560
|
+
setParserName(smaliParametersLabelParser, 'smaliParametersLabelParser');
|
|
1561
|
+
|
|
1562
|
+
const smaliParametersTypeParser: Parser<string, string> = cloneParser(smaliTypeDescriptorParser);
|
|
1563
|
+
|
|
1564
|
+
setParserName(smaliParametersTypeParser, 'smaliParametersTypeParser');
|
|
1565
|
+
|
|
1566
|
+
const smaliParametersFieldParser: Parser<DalvikExecutableField, string> = promiseCompose(
|
|
1567
|
+
createTupleParser([
|
|
1568
|
+
smaliTypeDescriptorParser,
|
|
1569
|
+
createExactSequenceParser('->'),
|
|
1570
|
+
smaliMemberNameParser,
|
|
1571
|
+
createExactSequenceParser(':'),
|
|
1572
|
+
smaliTypeDescriptorParser,
|
|
1573
|
+
]),
|
|
1574
|
+
([
|
|
1575
|
+
classPath,
|
|
1576
|
+
_separator,
|
|
1577
|
+
fieldName,
|
|
1578
|
+
_colon,
|
|
1579
|
+
type,
|
|
1580
|
+
]) => ({
|
|
1581
|
+
class: classPath,
|
|
1582
|
+
name: fieldName,
|
|
1583
|
+
type,
|
|
1584
|
+
}),
|
|
1585
|
+
);
|
|
1586
|
+
|
|
1587
|
+
setParserName(smaliParametersFieldParser, 'smaliParametersFieldParser');
|
|
1588
|
+
|
|
1589
|
+
type SmaliCodeOperationParameter =
|
|
1590
|
+
| SmaliRegister
|
|
1591
|
+
| SmaliRegister[]
|
|
1592
|
+
| string
|
|
1593
|
+
| DalvikExecutableMethod
|
|
1594
|
+
;
|
|
1595
|
+
|
|
1596
|
+
const smaliCodeOperationParametersParser: Parser<SmaliCodeOperationParameter[], string> = createArrayParser(promiseCompose(
|
|
1597
|
+
createTupleParser([
|
|
1598
|
+
createUnionParser<SmaliCodeOperationParameter, string>([
|
|
1599
|
+
smaliParametersRegisterParser,
|
|
1600
|
+
smaliParametersRegistersParser,
|
|
1601
|
+
smaliParametersStringParser,
|
|
1602
|
+
smaliParametersIntegerParser,
|
|
1603
|
+
promiseCompose(
|
|
1604
|
+
createTupleParser([
|
|
1605
|
+
createNegativeLookaheadParser(smaliParametersIntegerParser),
|
|
1606
|
+
smaliNumberParser,
|
|
1607
|
+
]),
|
|
1608
|
+
([ _notInteger, number ]) => number,
|
|
1609
|
+
),
|
|
1610
|
+
smaliParametersLabelParser,
|
|
1611
|
+
promiseCompose(
|
|
1612
|
+
createTupleParser([
|
|
1613
|
+
createNegativeLookaheadParser(smaliParametersMethodParser),
|
|
1614
|
+
createNegativeLookaheadParser(smaliParametersFieldParser),
|
|
1615
|
+
smaliParametersTypeParser,
|
|
1616
|
+
]),
|
|
1617
|
+
([
|
|
1618
|
+
_notMethod,
|
|
1619
|
+
_notField,
|
|
1620
|
+
type,
|
|
1621
|
+
]) => type,
|
|
1622
|
+
),
|
|
1623
|
+
smaliParametersMethodParser,
|
|
1624
|
+
smaliParametersFieldParser,
|
|
1625
|
+
]),
|
|
1626
|
+
createOptionalParser(createExactSequenceParser(', ')),
|
|
1627
|
+
]),
|
|
1628
|
+
([
|
|
1629
|
+
parameter,
|
|
1630
|
+
_comma,
|
|
1631
|
+
]) => parameter,
|
|
1632
|
+
));
|
|
1633
|
+
|
|
1634
|
+
setParserName(smaliCodeOperationParametersParser, 'smaliCodeOperationParametersParser');
|
|
1635
|
+
|
|
1636
|
+
const smaliCodeOperationNameParser: Parser<string, string> = promiseCompose(
|
|
1637
|
+
createTupleParser([
|
|
1638
|
+
promiseCompose(
|
|
1639
|
+
createSeparatedArrayParser(
|
|
1640
|
+
smaliIdentifierParser,
|
|
1641
|
+
createExactSequenceParser('-'),
|
|
1642
|
+
),
|
|
1643
|
+
parts => parts.join('-'),
|
|
1644
|
+
),
|
|
1645
|
+
createOptionalParser(promiseCompose(
|
|
1646
|
+
createTupleParser([
|
|
1647
|
+
createExactSequenceParser('/'),
|
|
1648
|
+
smaliIdentifierParser,
|
|
1649
|
+
]),
|
|
1650
|
+
([ slash, name ]) => slash + name,
|
|
1651
|
+
)),
|
|
1652
|
+
]),
|
|
1653
|
+
([
|
|
1654
|
+
name,
|
|
1655
|
+
optionalSlashName,
|
|
1656
|
+
]) => name + (optionalSlashName || ''),
|
|
1657
|
+
);
|
|
1658
|
+
|
|
1659
|
+
setParserName(smaliCodeOperationNameParser, 'smaliCodeOperationNameParser');
|
|
1660
|
+
|
|
1661
|
+
type SmaliOneLineCodeOperation = {
|
|
1662
|
+
operation: string;
|
|
1663
|
+
parameters: SmaliCodeOperationParameter[];
|
|
1664
|
+
};
|
|
1665
|
+
|
|
1666
|
+
const smaliOneLineCodeOperationParser: Parser<SmaliOneLineCodeOperation, string> = promiseCompose(
|
|
1667
|
+
createTupleParser([
|
|
1668
|
+
smaliSingleIndentationParser,
|
|
1669
|
+
smaliCodeOperationNameParser,
|
|
1670
|
+
promiseCompose(
|
|
1671
|
+
createOptionalParser(createTupleParser([
|
|
1672
|
+
smaliSingleWhitespaceParser,
|
|
1673
|
+
smaliCodeOperationParametersParser,
|
|
1674
|
+
])),
|
|
1675
|
+
undefinedOrParameters => undefinedOrParameters === undefined ? [] : undefinedOrParameters[1],
|
|
1676
|
+
),
|
|
1677
|
+
smaliLineEndPraser,
|
|
1678
|
+
]),
|
|
1679
|
+
([
|
|
1680
|
+
_indent,
|
|
1681
|
+
operation,
|
|
1682
|
+
parameters,
|
|
1683
|
+
_newline,
|
|
1684
|
+
]) => ({
|
|
1685
|
+
operation,
|
|
1686
|
+
parameters,
|
|
1687
|
+
}),
|
|
1688
|
+
);
|
|
1689
|
+
|
|
1690
|
+
setParserName(smaliOneLineCodeOperationParser, 'smaliOneLineCodeOperationParser');
|
|
1691
|
+
|
|
1692
|
+
type SmaliCodeOperationLabelsBody = string[];
|
|
1693
|
+
|
|
1694
|
+
const createMultilineSmaliCodeOperationLabelsBodyParser: (operationName: string) => Parser<SmaliCodeOperationLabelsBody, string> = operationName => promiseCompose(
|
|
1695
|
+
createTupleParser([
|
|
1696
|
+
createArrayParser(promiseCompose(
|
|
1697
|
+
createTupleParser([
|
|
1698
|
+
smaliIndentationParser,
|
|
1699
|
+
smaliCodeLabelParser,
|
|
1700
|
+
smaliLineEndPraser,
|
|
1701
|
+
]),
|
|
1702
|
+
([
|
|
1703
|
+
_indentation,
|
|
1704
|
+
label,
|
|
1705
|
+
_newline,
|
|
1706
|
+
]) => label,
|
|
1707
|
+
)),
|
|
1708
|
+
smaliIndentationParser,
|
|
1709
|
+
createExactSequenceParser('.end '),
|
|
1710
|
+
createExactSequenceParser(operationName),
|
|
1711
|
+
smaliLineEndPraser,
|
|
1712
|
+
]),
|
|
1713
|
+
([
|
|
1714
|
+
labels,
|
|
1715
|
+
]) => labels,
|
|
1716
|
+
);
|
|
1717
|
+
|
|
1718
|
+
type SmaliCodeOperationLabelMapBody = Array<readonly [number | bigint, string]>;
|
|
1719
|
+
|
|
1720
|
+
const createMultilineSmaliCodeOperationLabelMapBodyParser: (operationName: string) => Parser<SmaliCodeOperationLabelMapBody, string> = operationName => promiseCompose(
|
|
1721
|
+
createTupleParser([
|
|
1722
|
+
createArrayParser(promiseCompose(
|
|
1723
|
+
createTupleParser([
|
|
1724
|
+
smaliIndentationParser,
|
|
1725
|
+
smaliParametersIntegerParser,
|
|
1726
|
+
createExactSequenceParser(' -> '),
|
|
1727
|
+
smaliCodeLabelParser,
|
|
1728
|
+
smaliLineEndPraser,
|
|
1729
|
+
]),
|
|
1730
|
+
([
|
|
1731
|
+
_indentation,
|
|
1732
|
+
key,
|
|
1733
|
+
_arrow,
|
|
1734
|
+
label,
|
|
1735
|
+
_newline,
|
|
1736
|
+
]) => [
|
|
1737
|
+
key,
|
|
1738
|
+
label,
|
|
1739
|
+
] as const,
|
|
1740
|
+
)),
|
|
1741
|
+
smaliIndentationParser,
|
|
1742
|
+
createExactSequenceParser('.end '),
|
|
1743
|
+
createExactSequenceParser(operationName),
|
|
1744
|
+
smaliLineEndPraser,
|
|
1745
|
+
]),
|
|
1746
|
+
([
|
|
1747
|
+
labels,
|
|
1748
|
+
]) => labels,
|
|
1749
|
+
);
|
|
1750
|
+
|
|
1751
|
+
type SmaliCodeOperationIntegersBody = Array<number | bigint>;
|
|
1752
|
+
|
|
1753
|
+
const createMultilineSmaliCodeOperationIntegersBodyParser: (operationName: string) => Parser<SmaliCodeOperationIntegersBody, string> = operationName => promiseCompose(
|
|
1754
|
+
createTupleParser([
|
|
1755
|
+
createArrayParser(promiseCompose(
|
|
1756
|
+
createTupleParser([
|
|
1757
|
+
smaliIndentationParser,
|
|
1758
|
+
smaliParametersIntegerParser,
|
|
1759
|
+
smaliLineEndPraser,
|
|
1760
|
+
]),
|
|
1761
|
+
([
|
|
1762
|
+
_indentation,
|
|
1763
|
+
key,
|
|
1764
|
+
]) => key,
|
|
1765
|
+
)),
|
|
1766
|
+
smaliIndentationParser,
|
|
1767
|
+
createExactSequenceParser('.end '),
|
|
1768
|
+
createExactSequenceParser(operationName),
|
|
1769
|
+
smaliLineEndPraser,
|
|
1770
|
+
]),
|
|
1771
|
+
([
|
|
1772
|
+
labels,
|
|
1773
|
+
]) => labels,
|
|
1774
|
+
);
|
|
1775
|
+
|
|
1776
|
+
type SmaliCodeOperationBody =
|
|
1777
|
+
| SmaliCodeOperationLabelsBody
|
|
1778
|
+
| SmaliCodeOperationIntegersBody
|
|
1779
|
+
| SmaliCodeOperationLabelMapBody
|
|
1780
|
+
;
|
|
1781
|
+
|
|
1782
|
+
const createMultilineSmaliCodeOperationBodyParser: (operationName: string) => Parser<SmaliCodeOperationBody, string> = operationName => createUnionParser([
|
|
1783
|
+
createMultilineSmaliCodeOperationLabelsBodyParser(operationName),
|
|
1784
|
+
createMultilineSmaliCodeOperationIntegersBodyParser(operationName),
|
|
1785
|
+
createMultilineSmaliCodeOperationLabelMapBodyParser(operationName),
|
|
1786
|
+
]);
|
|
1787
|
+
|
|
1788
|
+
type SmaliMultilineCodeOperation = {
|
|
1789
|
+
operation: string;
|
|
1790
|
+
parameters: SmaliCodeOperationParameter[];
|
|
1791
|
+
body: SmaliCodeOperationBody;
|
|
1792
|
+
};
|
|
1793
|
+
|
|
1794
|
+
const smaliMultilineCodeOperationParser: Parser<SmaliMultilineCodeOperation, string> = parserCreatorCompose(
|
|
1795
|
+
() => promiseCompose(
|
|
1796
|
+
createTupleParser([
|
|
1797
|
+
smaliSingleIndentationParser,
|
|
1798
|
+
createExactSequenceParser('.'),
|
|
1799
|
+
smaliCodeOperationNameParser,
|
|
1800
|
+
promiseCompose(
|
|
1801
|
+
createOptionalParser(createTupleParser([
|
|
1802
|
+
smaliSingleWhitespaceParser,
|
|
1803
|
+
smaliCodeOperationParametersParser,
|
|
1804
|
+
])),
|
|
1805
|
+
undefinedOrParameters => undefinedOrParameters === undefined ? [] : undefinedOrParameters[1],
|
|
1806
|
+
),
|
|
1807
|
+
smaliLineEndPraser,
|
|
1808
|
+
]),
|
|
1809
|
+
([
|
|
1810
|
+
_indent,
|
|
1811
|
+
_dot,
|
|
1812
|
+
operation,
|
|
1813
|
+
parameters,
|
|
1814
|
+
_newline,
|
|
1815
|
+
]) => ({
|
|
1816
|
+
operation,
|
|
1817
|
+
parameters,
|
|
1818
|
+
}),
|
|
1819
|
+
),
|
|
1820
|
+
({ operation, parameters }) => promiseCompose(
|
|
1821
|
+
createMultilineSmaliCodeOperationBodyParser(operation),
|
|
1822
|
+
body => ({
|
|
1823
|
+
operation,
|
|
1824
|
+
parameters,
|
|
1825
|
+
body,
|
|
1826
|
+
}),
|
|
1827
|
+
),
|
|
1828
|
+
)();
|
|
1829
|
+
|
|
1830
|
+
setParserName(smaliMultilineCodeOperationParser, 'smaliMultilineCodeOperationParser');
|
|
1831
|
+
|
|
1832
|
+
type SmaliLooseCodeOperation =
|
|
1833
|
+
| SmaliOneLineCodeOperation
|
|
1834
|
+
| SmaliMultilineCodeOperation
|
|
1835
|
+
;
|
|
1836
|
+
|
|
1837
|
+
const smaliLooseCodeOperationParser: Parser<SmaliLooseCodeOperation, string> = createUnionParser([
|
|
1838
|
+
smaliOneLineCodeOperationParser,
|
|
1839
|
+
smaliMultilineCodeOperationParser,
|
|
1840
|
+
]);
|
|
1841
|
+
|
|
1842
|
+
setParserName(smaliLooseCodeOperationParser, 'smaliLooseCodeOperationParser');
|
|
1843
|
+
|
|
1844
|
+
type SmaliCodeOperationFromDalvikBytecodeOperation<T extends DalvikBytecodeOperation> =
|
|
1845
|
+
T extends { branchOffsets: number[] }
|
|
1846
|
+
? Simplify<Omit<T, 'branchOffsets'> & { branchOffsetIndices: number[] }>
|
|
1847
|
+
: T extends { branchOffset: number }
|
|
1848
|
+
? Simplify<Omit<T, 'branchOffset'> & { branchOffsetIndex: number }>
|
|
1849
|
+
: T extends { methodIndex: IndexIntoMethodIds }
|
|
1850
|
+
? Simplify<Omit<T, 'methodIndex'> & { method: DalvikExecutableMethod }>
|
|
1851
|
+
: T
|
|
1852
|
+
;
|
|
1853
|
+
|
|
1854
|
+
type SmaliCodeOperation = SmaliCodeOperationFromDalvikBytecodeOperation<DalvikBytecodeOperation>;
|
|
1855
|
+
|
|
1856
|
+
export const smaliCodeOperationParser: Parser<SmaliCodeOperation, string> = promiseCompose(
|
|
1857
|
+
smaliLooseCodeOperationParser,
|
|
1858
|
+
operation => {
|
|
1859
|
+
const operation_ = {
|
|
1860
|
+
operation: operation.operation,
|
|
1861
|
+
// Line,
|
|
1862
|
+
// local,
|
|
1863
|
+
// parameters: (operation as any).parameters,
|
|
1864
|
+
} as any; // TODO
|
|
1865
|
+
|
|
1866
|
+
if ('body' in operation) {
|
|
1867
|
+
const operationDexName = payloadOperationSmaliNameToDexName.get(operation.operation);
|
|
1868
|
+
|
|
1869
|
+
invariant(operationDexName, 'Unknown payload operation for %s', operation.operation);
|
|
1870
|
+
|
|
1871
|
+
operation_.operation = operationDexName;
|
|
1872
|
+
|
|
1873
|
+
if (operationDexName === 'sparse-switch-payload') {
|
|
1874
|
+
const map = new Map<number | bigint, string>(operation.body as SmaliCodeOperationLabelMapBody);
|
|
1875
|
+
operation_.keys = [ ...map.keys() ];
|
|
1876
|
+
operation_.branchLabels = [ ...map.values() ];
|
|
1877
|
+
} else if (operationDexName === 'fill-array-data-payload') {
|
|
1878
|
+
// Get elementWidth from parameters (first parameter)
|
|
1879
|
+
const elementWidth = operation.parameters[0];
|
|
1880
|
+
invariant(typeof elementWidth === 'number', 'Expected elementWidth to be a number');
|
|
1881
|
+
|
|
1882
|
+
operation_.elementWidth = elementWidth;
|
|
1883
|
+
|
|
1884
|
+
// Convert integer values to bytes (little-endian)
|
|
1885
|
+
const values = operation.body as SmaliCodeOperationIntegersBody;
|
|
1886
|
+
const data: number[] = [];
|
|
1887
|
+
|
|
1888
|
+
for (const value of values) {
|
|
1889
|
+
// Use BigInt for bitwise operations to preserve full precision
|
|
1890
|
+
const bigIntValue = typeof value === 'bigint' ? value : BigInt(value);
|
|
1891
|
+
|
|
1892
|
+
// Convert to bytes based on elementWidth (little-endian)
|
|
1893
|
+
for (let i = 0; i < elementWidth; i++) {
|
|
1894
|
+
data.push(Number((bigIntValue >> BigInt(i * 8)) & 0xFFn));
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
operation_.data = data;
|
|
1899
|
+
} else {
|
|
1900
|
+
operation_.branchLabels = operation.body;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
const hasRegisters = Array.isArray(operation.parameters.at(0));
|
|
1905
|
+
if (hasRegisters) {
|
|
1906
|
+
operation_.registers = [];
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
for (const parameter of (operation as unknown as { parameters: unknown[] }).parameters.flat() ?? []) {
|
|
1910
|
+
if (isDalvikExecutableMethod(parameter)) {
|
|
1911
|
+
operation_.method = parameter;
|
|
1912
|
+
|
|
1913
|
+
continue;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
if (isDalvikExecutableField(parameter)) {
|
|
1917
|
+
operation_.field = parameter;
|
|
1918
|
+
|
|
1919
|
+
continue;
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
if (isSmaliRegister(parameter)) {
|
|
1923
|
+
operation_.registers ||= [];
|
|
1924
|
+
|
|
1925
|
+
operation_.registers.push(parameter);
|
|
1926
|
+
|
|
1927
|
+
continue;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
if (typeof parameter === 'string') {
|
|
1931
|
+
if (operationsWithTypeArgument.has(operation_.operation)) {
|
|
1932
|
+
operation_.type = parameter;
|
|
1933
|
+
|
|
1934
|
+
continue;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
if (operationsWithBranchLabelArgument.has(operation_.operation)) {
|
|
1938
|
+
operation_.branchLabel = parameter;
|
|
1939
|
+
|
|
1940
|
+
continue;
|
|
1941
|
+
}
|
|
1942
|
+
|
|
1943
|
+
operation_.string = parameter;
|
|
1944
|
+
|
|
1945
|
+
continue;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
if (typeof parameter === 'number' || typeof parameter === 'bigint') {
|
|
1949
|
+
// Skip for fill-array-data-payload, already handled in body processing
|
|
1950
|
+
if (operation_.operation === 'fill-array-data-payload') {
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
// Const-wide operations always use bigint values
|
|
1955
|
+
if (operationsWithBigintValue.has(operation_.operation)) {
|
|
1956
|
+
operation_.value = typeof parameter === 'number' ? BigInt(parameter) : parameter;
|
|
1957
|
+
} else {
|
|
1958
|
+
operation_.value = parameter;
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
continue;
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
invariant(false, 'TODO: parameter: %s, operation: %s', JSON.stringify(parameter), JSON.stringify(operation));
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
return operation_;
|
|
1968
|
+
},
|
|
1969
|
+
);
|
|
1970
|
+
|
|
1971
|
+
setParserName(smaliCodeOperationParser, 'smaliCodeOperationParser');
|
|
1972
|
+
|
|
1973
|
+
const operationsWithTypeArgument = new Set<DalvikBytecodeOperation['operation']>([
|
|
1974
|
+
'new-instance',
|
|
1975
|
+
'new-array',
|
|
1976
|
+
'filled-new-array',
|
|
1977
|
+
'filled-new-array/range',
|
|
1978
|
+
'check-cast',
|
|
1979
|
+
'instance-of',
|
|
1980
|
+
'const-class',
|
|
1981
|
+
]);
|
|
1982
|
+
|
|
1983
|
+
const operationsWithBigintValue = new Set<DalvikBytecodeOperation['operation']>([
|
|
1984
|
+
'const-wide/16',
|
|
1985
|
+
'const-wide/32',
|
|
1986
|
+
'const-wide',
|
|
1987
|
+
]);
|
|
1988
|
+
|
|
1989
|
+
const operationsWithBranchLabelArgument = new Set<DalvikBytecodeOperation['operation']>([
|
|
1990
|
+
'goto',
|
|
1991
|
+
'goto/16',
|
|
1992
|
+
'goto/32',
|
|
1993
|
+
'if-eq',
|
|
1994
|
+
'if-ne',
|
|
1995
|
+
'if-lt',
|
|
1996
|
+
'if-ge',
|
|
1997
|
+
'if-gt',
|
|
1998
|
+
'if-le',
|
|
1999
|
+
'if-eqz',
|
|
2000
|
+
'if-nez',
|
|
2001
|
+
'if-ltz',
|
|
2002
|
+
'if-gez',
|
|
2003
|
+
'if-gtz',
|
|
2004
|
+
'if-lez',
|
|
2005
|
+
'packed-switch',
|
|
2006
|
+
'sparse-switch',
|
|
2007
|
+
'fill-array-data',
|
|
2008
|
+
]);
|
|
2009
|
+
|
|
2010
|
+
const payloadOperationSmaliNameToDexName = new Map<string, string>(Object.entries({
|
|
2011
|
+
'packed-switch': 'packed-switch-payload',
|
|
2012
|
+
'sparse-switch': 'sparse-switch-payload',
|
|
2013
|
+
'array-data': 'fill-array-data-payload',
|
|
2014
|
+
}));
|
|
2015
|
+
|
|
2016
|
+
function isOperationWithLabels(value: unknown): value is DalvikBytecodeOperation & {
|
|
2017
|
+
labels: Set<string>;
|
|
2018
|
+
} {
|
|
2019
|
+
return (
|
|
2020
|
+
typeof value === 'object'
|
|
2021
|
+
&& value !== null
|
|
2022
|
+
&& 'labels' in value
|
|
2023
|
+
&& value.labels instanceof Set
|
|
2024
|
+
);
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
type SmaliDebugDirective =
|
|
2028
|
+
| { type: 'line'; line: number }
|
|
2029
|
+
| { type: 'startLocal'; local: SmaliCodeLocal }
|
|
2030
|
+
| { type: 'endLocal'; register: SmaliRegister }
|
|
2031
|
+
| { type: 'restartLocal'; register: SmaliRegister }
|
|
2032
|
+
| { type: 'setPrologueEnd' }
|
|
2033
|
+
| { type: 'setEpilogueBegin' };
|
|
2034
|
+
|
|
2035
|
+
type SmaliAnnotatedCodeOperation = {
|
|
2036
|
+
debugDirectives: SmaliDebugDirective[];
|
|
2037
|
+
labels: string[];
|
|
2038
|
+
operation: SmaliCodeOperation;
|
|
2039
|
+
};
|
|
2040
|
+
|
|
2041
|
+
const smaliAnnotatedCodeOperationParser: Parser<SmaliAnnotatedCodeOperation, string> = promiseCompose(
|
|
2042
|
+
createTupleParser([
|
|
2043
|
+
createArrayParser(smaliCodeLineParser),
|
|
2044
|
+
createOptionalParser(smaliCodeLocalParser),
|
|
2045
|
+
createOptionalParser(smaliCodeEndLocalParser),
|
|
2046
|
+
createOptionalParser(smaliCodeRestartLocalParser),
|
|
2047
|
+
createOptionalParser(smaliCodePrologueEndParser),
|
|
2048
|
+
createOptionalParser(smaliCodeEpilogueBeginParser),
|
|
2049
|
+
createArrayParser(smaliCodeLabelLineParser),
|
|
2050
|
+
smaliCodeOperationParser,
|
|
2051
|
+
]),
|
|
2052
|
+
([
|
|
2053
|
+
lines,
|
|
2054
|
+
local,
|
|
2055
|
+
endLocal,
|
|
2056
|
+
restartLocal,
|
|
2057
|
+
prologueEnd,
|
|
2058
|
+
epilogueBegin,
|
|
2059
|
+
labels,
|
|
2060
|
+
operation,
|
|
2061
|
+
]) => {
|
|
2062
|
+
const debugDirectives: SmaliDebugDirective[] = [];
|
|
2063
|
+
|
|
2064
|
+
for (const line of lines) {
|
|
2065
|
+
debugDirectives.push({ type: 'line', line });
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
if (local) {
|
|
2069
|
+
debugDirectives.push({ type: 'startLocal', local });
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
if (endLocal) {
|
|
2073
|
+
debugDirectives.push({ type: 'endLocal', register: endLocal });
|
|
2074
|
+
}
|
|
2075
|
+
|
|
2076
|
+
if (restartLocal) {
|
|
2077
|
+
debugDirectives.push({ type: 'restartLocal', register: restartLocal });
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
if (prologueEnd) {
|
|
2081
|
+
debugDirectives.push({ type: 'setPrologueEnd' });
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
if (epilogueBegin) {
|
|
2085
|
+
debugDirectives.push({ type: 'setEpilogueBegin' });
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
return {
|
|
2089
|
+
debugDirectives,
|
|
2090
|
+
labels,
|
|
2091
|
+
operation,
|
|
2092
|
+
};
|
|
2093
|
+
},
|
|
2094
|
+
);
|
|
2095
|
+
|
|
2096
|
+
setParserName(smaliAnnotatedCodeOperationParser, 'smaliAnnotatedCodeOperationParser');
|
|
2097
|
+
|
|
2098
|
+
type SmaliExecutableCode<DalvikBytecode> = {
|
|
2099
|
+
dalvikExecutableCode: DalvikExecutableCode<DalvikBytecode>;
|
|
2100
|
+
parameters: SmaliCodeParameter[];
|
|
2101
|
+
parameterAnnotations: SmaliCodeParameter[]; // TODO?: SmaliAnnotation[]
|
|
2102
|
+
methodAnnotations: SmaliAnnotation[];
|
|
2103
|
+
};
|
|
2104
|
+
|
|
2105
|
+
const smaliExecutableCodeParser: Parser<SmaliExecutableCode<DalvikBytecode>, string> = promiseCompose(
|
|
2106
|
+
createTupleParser([
|
|
2107
|
+
createOptionalParser(smaliCodeRegistersParser),
|
|
2108
|
+
createArrayParser(smaliAnnotationParser),
|
|
2109
|
+
createArrayParser(smaliCodeParameterParser),
|
|
2110
|
+
createOptionalParser(smaliCommentsOrNewlinesParser),
|
|
2111
|
+
createSeparatedArrayParser(
|
|
2112
|
+
smaliAnnotationParser,
|
|
2113
|
+
smaliCommentsOrNewlinesParser,
|
|
2114
|
+
),
|
|
2115
|
+
createArrayParser(promiseCompose(
|
|
2116
|
+
createTupleParser([
|
|
2117
|
+
createOptionalParser(smaliCommentsOrNewlinesParser),
|
|
2118
|
+
createDisjunctionParser([
|
|
2119
|
+
smaliIndentedCommentParser,
|
|
2120
|
+
smaliAnnotatedCodeOperationParser,
|
|
2121
|
+
smaliLabeledCatchDirectiveParser,
|
|
2122
|
+
]),
|
|
2123
|
+
]),
|
|
2124
|
+
([
|
|
2125
|
+
_commentsOrNewlinesParser,
|
|
2126
|
+
operation,
|
|
2127
|
+
]) => operation,
|
|
2128
|
+
)),
|
|
2129
|
+
]),
|
|
2130
|
+
([
|
|
2131
|
+
registersSize,
|
|
2132
|
+
annotations1,
|
|
2133
|
+
parameters,
|
|
2134
|
+
_leadingCommentsOrNewlines,
|
|
2135
|
+
annotations2,
|
|
2136
|
+
instructionsAndCatchDirectives,
|
|
2137
|
+
]) => {
|
|
2138
|
+
const annotations = [ ...annotations1, ...annotations2 ];
|
|
2139
|
+
const instructions: SmaliCodeOperation[] = [];
|
|
2140
|
+
const annotatedOperations: SmaliAnnotatedCodeOperation[] = [];
|
|
2141
|
+
const catchDirectives: SmaliCatchDirective[] = [];
|
|
2142
|
+
const catchDirectiveLabels: Map<SmaliCatchDirective, string[]> = new Map();
|
|
2143
|
+
|
|
2144
|
+
for (const item of instructionsAndCatchDirectives) {
|
|
2145
|
+
if (item && typeof item === 'object') {
|
|
2146
|
+
if ('labels' in item && 'catchDirective' in item) {
|
|
2147
|
+
// This is a SmaliLabeledCatchDirective
|
|
2148
|
+
const labeledCatch = item as SmaliLabeledCatchDirective;
|
|
2149
|
+
catchDirectives.push(labeledCatch.catchDirective);
|
|
2150
|
+
catchDirectiveLabels.set(labeledCatch.catchDirective, labeledCatch.labels);
|
|
2151
|
+
} else if ('type' in item && 'startLabel' in item && 'endLabel' in item && 'handlerLabel' in item) {
|
|
2152
|
+
// This is a bare SmaliCatchDirective (shouldn't happen with current parser structure)
|
|
2153
|
+
catchDirectives.push(item as SmaliCatchDirective);
|
|
2154
|
+
} else if ('debugDirectives' in item && 'labels' in item && 'operation' in item) {
|
|
2155
|
+
// This is a SmaliAnnotatedCodeOperation
|
|
2156
|
+
const annotated = item as SmaliAnnotatedCodeOperation;
|
|
2157
|
+
annotatedOperations.push(annotated);
|
|
2158
|
+
const operation = annotated.operation;
|
|
2159
|
+
if (annotated.labels.length > 0) {
|
|
2160
|
+
(operation as any).labels = new Set(annotated.labels);
|
|
2161
|
+
}
|
|
2162
|
+
instructions.push(operation);
|
|
2163
|
+
} else if (item !== undefined) {
|
|
2164
|
+
// This is a SmaliCodeOperation (fallback)
|
|
2165
|
+
instructions.push(item as SmaliCodeOperation);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
}
|
|
2169
|
+
|
|
2170
|
+
if (
|
|
2171
|
+
registersSize === undefined
|
|
2172
|
+
&& parameters !== undefined
|
|
2173
|
+
) {
|
|
2174
|
+
registersSize = parameters.length;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
invariant(
|
|
2178
|
+
registersSize !== undefined,
|
|
2179
|
+
'Expected registers size to be defined',
|
|
2180
|
+
);
|
|
2181
|
+
|
|
2182
|
+
for (const [ operationIndex, operation ] of instructions.entries()) {
|
|
2183
|
+
if (
|
|
2184
|
+
'branchLabel' in operation
|
|
2185
|
+
&& typeof operation.branchLabel === 'string'
|
|
2186
|
+
) {
|
|
2187
|
+
for (const [ targetOperationIndex, targetOperation ] of instructions.entries()) {
|
|
2188
|
+
if (!isOperationWithLabels(targetOperation)) {
|
|
2189
|
+
continue;
|
|
2190
|
+
}
|
|
2191
|
+
|
|
2192
|
+
if (!targetOperation.labels.has(operation.branchLabel)) {
|
|
2193
|
+
continue;
|
|
2194
|
+
}
|
|
2195
|
+
|
|
2196
|
+
delete (operation as any).branchLabel;
|
|
2197
|
+
(operation as any).branchOffsetIndex = targetOperationIndex - operationIndex;
|
|
2198
|
+
|
|
2199
|
+
break;
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
if (
|
|
2204
|
+
'branchLabels' in operation
|
|
2205
|
+
&& typeof operation.branchLabels === 'object'
|
|
2206
|
+
&& Array.isArray(operation.branchLabels)
|
|
2207
|
+
) {
|
|
2208
|
+
const sourceOperations = instructions.filter((sourceOperation, sourceOperationIndex) => (
|
|
2209
|
+
'branchOffsetIndex' in sourceOperation
|
|
2210
|
+
&& typeof sourceOperation.branchOffsetIndex === 'number'
|
|
2211
|
+
&& sourceOperation.branchOffsetIndex + sourceOperationIndex === operationIndex
|
|
2212
|
+
));
|
|
2213
|
+
|
|
2214
|
+
invariant(
|
|
2215
|
+
sourceOperations.length === 1,
|
|
2216
|
+
'Expected exactly one source operation to point to %s',
|
|
2217
|
+
JSON.stringify(operation),
|
|
2218
|
+
);
|
|
2219
|
+
|
|
2220
|
+
const [ sourceOperation ] = sourceOperations;
|
|
2221
|
+
const sourceOperationIndex = instructions.indexOf(sourceOperation);
|
|
2222
|
+
|
|
2223
|
+
(operation as any).branchOffsetIndices = operation.branchLabels.map((branchLabel: string) => {
|
|
2224
|
+
for (const [ targetOperationIndex, targetOperation ] of instructions.entries()) {
|
|
2225
|
+
if (!isOperationWithLabels(targetOperation)) {
|
|
2226
|
+
continue;
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
if (!targetOperation.labels.has(branchLabel)) {
|
|
2230
|
+
continue;
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
return targetOperationIndex - sourceOperationIndex;
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
invariant(false, 'Expected to find branch label %s', branchLabel);
|
|
2237
|
+
});
|
|
2238
|
+
|
|
2239
|
+
delete (operation as any).branchLabels;
|
|
2240
|
+
}
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
const branchOffsetByBranchOffsetIndex = new Map<number, number>();
|
|
2244
|
+
|
|
2245
|
+
let operationOffset = 0;
|
|
2246
|
+
|
|
2247
|
+
for (const [ operationIndex, operation ] of instructions.entries()) {
|
|
2248
|
+
const operationSize = getOperationFormatSize(operation);
|
|
2249
|
+
|
|
2250
|
+
branchOffsetByBranchOffsetIndex.set(
|
|
2251
|
+
operationIndex,
|
|
2252
|
+
operationOffset,
|
|
2253
|
+
);
|
|
2254
|
+
|
|
2255
|
+
operationOffset += operationSize;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
for (const [ operationIndex, operation ] of instructions.entries()) {
|
|
2259
|
+
if (
|
|
2260
|
+
'branchOffsetIndex' in operation
|
|
2261
|
+
&& typeof operation.branchOffsetIndex === 'number'
|
|
2262
|
+
) {
|
|
2263
|
+
const operationOffset = branchOffsetByBranchOffsetIndex.get(operationIndex + operation.branchOffsetIndex);
|
|
2264
|
+
invariant(
|
|
2265
|
+
operationOffset !== undefined,
|
|
2266
|
+
'Expected branch offset for operation index %s, but got undefined',
|
|
2267
|
+
operation.branchOffsetIndex,
|
|
2268
|
+
);
|
|
2269
|
+
|
|
2270
|
+
const branchOffset = branchOffsetByBranchOffsetIndex.get(operationIndex);
|
|
2271
|
+
invariant(
|
|
2272
|
+
branchOffset !== undefined,
|
|
2273
|
+
'Expected branch offset for operation index %s, but got undefined',
|
|
2274
|
+
operationIndex,
|
|
2275
|
+
);
|
|
2276
|
+
|
|
2277
|
+
(operation as any).branchOffset = operationOffset - branchOffset;
|
|
2278
|
+
}
|
|
2279
|
+
|
|
2280
|
+
if (
|
|
2281
|
+
'branchOffsetIndices' in operation
|
|
2282
|
+
&& typeof operation.branchOffsetIndices === 'object'
|
|
2283
|
+
&& Array.isArray(operation.branchOffsetIndices)
|
|
2284
|
+
) {
|
|
2285
|
+
const sourceOperations = instructions.filter((sourceOperation, sourceOperationIndex) => (
|
|
2286
|
+
'branchOffsetIndex' in sourceOperation
|
|
2287
|
+
&& typeof sourceOperation.branchOffsetIndex === 'number'
|
|
2288
|
+
&& sourceOperation.branchOffsetIndex + sourceOperationIndex === operationIndex
|
|
2289
|
+
));
|
|
2290
|
+
|
|
2291
|
+
invariant(
|
|
2292
|
+
sourceOperations.length === 1,
|
|
2293
|
+
'Expected exactly one source operation to point to %s',
|
|
2294
|
+
JSON.stringify(operation),
|
|
2295
|
+
);
|
|
2296
|
+
|
|
2297
|
+
const [ sourceOperation ] = sourceOperations;
|
|
2298
|
+
const sourceOperationIndex = instructions.indexOf(sourceOperation);
|
|
2299
|
+
|
|
2300
|
+
(operation as any).branchOffsets = operation.branchOffsetIndices.map((branchOffsetIndex: number) => {
|
|
2301
|
+
const operationOffset = branchOffsetByBranchOffsetIndex.get(sourceOperationIndex + branchOffsetIndex);
|
|
2302
|
+
invariant(
|
|
2303
|
+
operationOffset !== undefined,
|
|
2304
|
+
'Expected branch offset for operation index %s, but got undefined',
|
|
2305
|
+
sourceOperationIndex + branchOffsetIndex,
|
|
2306
|
+
);
|
|
2307
|
+
|
|
2308
|
+
const branchOffset = branchOffsetByBranchOffsetIndex.get(sourceOperationIndex);
|
|
2309
|
+
invariant(
|
|
2310
|
+
branchOffset !== undefined,
|
|
2311
|
+
'Expected branch offset for operation index %s, but got undefined',
|
|
2312
|
+
sourceOperationIndex,
|
|
2313
|
+
);
|
|
2314
|
+
|
|
2315
|
+
return operationOffset - branchOffset;
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
// Build label-to-index mapping
|
|
2321
|
+
// Labels attached to instructions map to that instruction's index
|
|
2322
|
+
// Labels before catch directives should map to the position they mark
|
|
2323
|
+
const labelToIndexMap = new Map<string, number>();
|
|
2324
|
+
|
|
2325
|
+
// First, map labels from instructions
|
|
2326
|
+
for (const [ operationIndex, operation ] of instructions.entries()) {
|
|
2327
|
+
if (
|
|
2328
|
+
operation
|
|
2329
|
+
&& typeof operation === 'object'
|
|
2330
|
+
&& 'labels' in operation
|
|
2331
|
+
&& operation.labels instanceof Set
|
|
2332
|
+
) {
|
|
2333
|
+
for (const label of operation.labels) {
|
|
2334
|
+
labelToIndexMap.set(label, operationIndex);
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
}
|
|
2338
|
+
|
|
2339
|
+
// Now handle labels from catch directives
|
|
2340
|
+
// We need to figure out where each catch directive appears in the original sequence
|
|
2341
|
+
let instructionArrayIndex = 0;
|
|
2342
|
+
for (let i = 0; i < instructionsAndCatchDirectives.length; i++) {
|
|
2343
|
+
const item = instructionsAndCatchDirectives[i];
|
|
2344
|
+
if (item && typeof item === 'object' && 'labels' in item && 'catchDirective' in item) {
|
|
2345
|
+
// This is a catch directive with labels
|
|
2346
|
+
// These labels should map to the current instruction index
|
|
2347
|
+
// (which is where the next instruction would be)
|
|
2348
|
+
const labeledCatch = item as SmaliLabeledCatchDirective;
|
|
2349
|
+
for (const label of labeledCatch.labels) {
|
|
2350
|
+
labelToIndexMap.set(label, instructionArrayIndex);
|
|
2351
|
+
}
|
|
2352
|
+
} else if (item !== undefined && !(item && typeof item === 'object' && 'type' in item && 'startLabel' in item)) {
|
|
2353
|
+
// This is an instruction, increment the counter
|
|
2354
|
+
instructionArrayIndex++;
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
// Build tries array from catch directives
|
|
2359
|
+
const triesByRange = new Map<string, {
|
|
2360
|
+
startAddress: number;
|
|
2361
|
+
instructionCount: number;
|
|
2362
|
+
handlers: Array<{ type: string; address: number }>;
|
|
2363
|
+
catchAllAddress: number | undefined;
|
|
2364
|
+
}>();
|
|
2365
|
+
|
|
2366
|
+
for (const catchDirective of catchDirectives) {
|
|
2367
|
+
// Find the start and end instruction indices
|
|
2368
|
+
const startIndex = labelToIndexMap.get(catchDirective.startLabel);
|
|
2369
|
+
const endIndex = labelToIndexMap.get(catchDirective.endLabel);
|
|
2370
|
+
const handlerIndex = labelToIndexMap.get(catchDirective.handlerLabel);
|
|
2371
|
+
|
|
2372
|
+
invariant(startIndex !== undefined, 'Expected to find start label %s', catchDirective.startLabel);
|
|
2373
|
+
invariant(endIndex !== undefined, 'Expected to find end label %s', catchDirective.endLabel);
|
|
2374
|
+
invariant(handlerIndex !== undefined, 'Expected to find handler label %s', catchDirective.handlerLabel);
|
|
2375
|
+
|
|
2376
|
+
const startAddress = branchOffsetByBranchOffsetIndex.get(startIndex);
|
|
2377
|
+
const endAddress = branchOffsetByBranchOffsetIndex.get(endIndex);
|
|
2378
|
+
const handlerAddress = branchOffsetByBranchOffsetIndex.get(handlerIndex);
|
|
2379
|
+
|
|
2380
|
+
invariant(startAddress !== undefined, 'Expected start address for index %s', startIndex);
|
|
2381
|
+
invariant(endAddress !== undefined, 'Expected end address for index %s', endIndex);
|
|
2382
|
+
invariant(handlerAddress !== undefined, 'Expected handler address for index %s', handlerIndex);
|
|
2383
|
+
|
|
2384
|
+
const instructionCount = endAddress - startAddress;
|
|
2385
|
+
const rangeKey = `${startAddress}-${instructionCount}`;
|
|
2386
|
+
|
|
2387
|
+
let tryEntry = triesByRange.get(rangeKey);
|
|
2388
|
+
if (!tryEntry) {
|
|
2389
|
+
tryEntry = {
|
|
2390
|
+
startAddress,
|
|
2391
|
+
instructionCount,
|
|
2392
|
+
handlers: [],
|
|
2393
|
+
catchAllAddress: undefined,
|
|
2394
|
+
};
|
|
2395
|
+
triesByRange.set(rangeKey, tryEntry);
|
|
2396
|
+
}
|
|
2397
|
+
|
|
2398
|
+
if (catchDirective.type === undefined) {
|
|
2399
|
+
// .catchall
|
|
2400
|
+
tryEntry.catchAllAddress = handlerAddress;
|
|
2401
|
+
} else {
|
|
2402
|
+
// .catch Type
|
|
2403
|
+
tryEntry.handlers.push({
|
|
2404
|
+
type: catchDirective.type,
|
|
2405
|
+
address: handlerAddress,
|
|
2406
|
+
});
|
|
2407
|
+
}
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2410
|
+
const tries = Array.from(triesByRange.values()).map(tryEntry => ({
|
|
2411
|
+
startAddress: tryEntry.startAddress,
|
|
2412
|
+
instructionCount: tryEntry.instructionCount,
|
|
2413
|
+
handler: {
|
|
2414
|
+
handlers: tryEntry.handlers,
|
|
2415
|
+
catchAllAddress: tryEntry.catchAllAddress,
|
|
2416
|
+
},
|
|
2417
|
+
}));
|
|
2418
|
+
|
|
2419
|
+
for (const operation of instructions) {
|
|
2420
|
+
delete (operation as any).labels;
|
|
2421
|
+
delete (operation as any).branchOffsetIndex;
|
|
2422
|
+
delete (operation as any).branchOffsetIndices;
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
// Build debug info from debug directives
|
|
2426
|
+
type DebugByteCodeValue = DalvikExecutableDebugInfo['bytecode'][number];
|
|
2427
|
+
let debugInfo: DalvikExecutableDebugInfo | undefined;
|
|
2428
|
+
const debugBytecode: DebugByteCodeValue[] = [];
|
|
2429
|
+
let currentLine: number | undefined;
|
|
2430
|
+
let lineStart: number | undefined;
|
|
2431
|
+
|
|
2432
|
+
// Helper to convert SmaliRegister to register number
|
|
2433
|
+
const getRegisterNum = (register: SmaliRegister): number => {
|
|
2434
|
+
// In Smali, 'v' registers start from 0, 'p' registers are parameters
|
|
2435
|
+
// For now, we'll just use the index. The actual conversion may need
|
|
2436
|
+
// to account for registersSize and parameter mapping.
|
|
2437
|
+
if (register.prefix === 'v') {
|
|
2438
|
+
return register.index;
|
|
2439
|
+
} else {
|
|
2440
|
+
// 'p' registers: need to map to actual register numbers
|
|
2441
|
+
// p0 starts at (registersSize - insSize)
|
|
2442
|
+
// For now, just use the index as we don't have accurate insSize
|
|
2443
|
+
return register.index;
|
|
2444
|
+
}
|
|
2445
|
+
};
|
|
2446
|
+
|
|
2447
|
+
// Helper to emit accumulated line/address changes as special opcode or explicit opcodes
|
|
2448
|
+
const emitPendingChanges = (lineDiff: number, addressDiff: number) => {
|
|
2449
|
+
// Special opcode formula: opcode = 0x0A + (line_diff + 4) + 15 * addr_diff
|
|
2450
|
+
// Valid when: line_diff ∈ [-4, 10] and addr_diff ∈ [0, 17] and result ≤ 0xFF
|
|
2451
|
+
if (lineDiff >= -4 && lineDiff <= 10 && addressDiff >= 0) {
|
|
2452
|
+
const adjusted = (lineDiff + 4) + 15 * addressDiff;
|
|
2453
|
+
const opcode = 0x0A + adjusted;
|
|
2454
|
+
if (opcode <= 0xFF) {
|
|
2455
|
+
debugBytecode.push({
|
|
2456
|
+
type: 'special',
|
|
2457
|
+
value: opcode,
|
|
2458
|
+
});
|
|
2459
|
+
return;
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
// Fall back to explicit opcodes
|
|
2464
|
+
if (addressDiff > 0) {
|
|
2465
|
+
debugBytecode.push({
|
|
2466
|
+
type: 'advancePc',
|
|
2467
|
+
addressDiff,
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
if (lineDiff !== 0) {
|
|
2471
|
+
debugBytecode.push({
|
|
2472
|
+
type: 'advanceLine',
|
|
2473
|
+
lineDiff,
|
|
2474
|
+
});
|
|
2475
|
+
}
|
|
2476
|
+
};
|
|
2477
|
+
|
|
2478
|
+
let accumulatedAddressDiff = 0;
|
|
2479
|
+
let accumulatedLineDiff = 0;
|
|
2480
|
+
let lastAddress = 0;
|
|
2481
|
+
let hasEmittedFirstDebugEvent = false;
|
|
2482
|
+
|
|
2483
|
+
for (let i = 0; i < annotatedOperations.length; i++) {
|
|
2484
|
+
const annotated = annotatedOperations[i];
|
|
2485
|
+
const operationAddress = branchOffsetByBranchOffsetIndex.get(i) ?? 0;
|
|
2486
|
+
|
|
2487
|
+
// Accumulate address difference (only after we've started tracking)
|
|
2488
|
+
if (i > 0 && hasEmittedFirstDebugEvent) {
|
|
2489
|
+
accumulatedAddressDiff += operationAddress - lastAddress;
|
|
2490
|
+
}
|
|
2491
|
+
lastAddress = operationAddress;
|
|
2492
|
+
|
|
2493
|
+
// Process line directives first to update accumulatedLineDiff
|
|
2494
|
+
for (const directive of annotated.debugDirectives) {
|
|
2495
|
+
if (directive.type === 'line') {
|
|
2496
|
+
if (lineStart === undefined) {
|
|
2497
|
+
lineStart = directive.line;
|
|
2498
|
+
currentLine = directive.line;
|
|
2499
|
+
} else if (currentLine !== undefined) {
|
|
2500
|
+
accumulatedLineDiff += directive.line - currentLine;
|
|
2501
|
+
currentLine = directive.line;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
// Emit accumulated changes before any other debug events
|
|
2507
|
+
const hasOtherDebugEvents = annotated.debugDirectives.some(d =>
|
|
2508
|
+
d.type !== 'line'
|
|
2509
|
+
);
|
|
2510
|
+
|
|
2511
|
+
if (hasOtherDebugEvents && (accumulatedLineDiff !== 0 || accumulatedAddressDiff !== 0 || !hasEmittedFirstDebugEvent)) {
|
|
2512
|
+
emitPendingChanges(accumulatedLineDiff, accumulatedAddressDiff);
|
|
2513
|
+
accumulatedLineDiff = 0;
|
|
2514
|
+
accumulatedAddressDiff = 0;
|
|
2515
|
+
hasEmittedFirstDebugEvent = true;
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
// Emit other debug directives
|
|
2519
|
+
for (const directive of annotated.debugDirectives) {
|
|
2520
|
+
if (directive.type === 'startLocal') {
|
|
2521
|
+
debugBytecode.push({
|
|
2522
|
+
type: 'startLocal',
|
|
2523
|
+
registerNum: getRegisterNum(directive.local.register),
|
|
2524
|
+
name: directive.local.name,
|
|
2525
|
+
type_: directive.local.type,
|
|
2526
|
+
});
|
|
2527
|
+
} else if (directive.type === 'endLocal') {
|
|
2528
|
+
debugBytecode.push({
|
|
2529
|
+
type: 'endLocal',
|
|
2530
|
+
registerNum: getRegisterNum(directive.register),
|
|
2531
|
+
});
|
|
2532
|
+
} else if (directive.type === 'restartLocal') {
|
|
2533
|
+
debugBytecode.push({
|
|
2534
|
+
type: 'restartLocal',
|
|
2535
|
+
registerNum: getRegisterNum(directive.register),
|
|
2536
|
+
});
|
|
2537
|
+
} else if (directive.type === 'setPrologueEnd') {
|
|
2538
|
+
debugBytecode.push({
|
|
2539
|
+
type: 'setPrologueEnd',
|
|
2540
|
+
});
|
|
2541
|
+
} else if (directive.type === 'setEpilogueBegin') {
|
|
2542
|
+
debugBytecode.push({
|
|
2543
|
+
type: 'setEpilogueBegin',
|
|
2544
|
+
});
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
|
|
2549
|
+
// Emit any remaining accumulated changes at the end
|
|
2550
|
+
if (accumulatedLineDiff !== 0 || accumulatedAddressDiff !== 0) {
|
|
2551
|
+
emitPendingChanges(accumulatedLineDiff, accumulatedAddressDiff);
|
|
2552
|
+
}
|
|
2553
|
+
|
|
2554
|
+
if (lineStart !== undefined) {
|
|
2555
|
+
// Extract parameter names from .param directives
|
|
2556
|
+
const parameterNames: Array<undefined | string> = [];
|
|
2557
|
+
// TODO: Extract from parameters array if needed
|
|
2558
|
+
|
|
2559
|
+
// If we have a lineStart but no bytecode, we need to emit a special opcode
|
|
2560
|
+
// to establish the initial state. The special value 0x0E (14) means:
|
|
2561
|
+
// adjusted = 14 - 10 = 4, line_diff = 4 % 15 - 4 = 0, pc_diff = 4 / 15 = 0
|
|
2562
|
+
if (debugBytecode.length === 0) {
|
|
2563
|
+
debugBytecode.push({
|
|
2564
|
+
type: 'special',
|
|
2565
|
+
value: 0x0A + 4, // 14 in decimal
|
|
2566
|
+
});
|
|
2567
|
+
}
|
|
2568
|
+
|
|
2569
|
+
debugInfo = {
|
|
2570
|
+
lineStart,
|
|
2571
|
+
parameterNames,
|
|
2572
|
+
bytecode: debugBytecode,
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
return {
|
|
2577
|
+
dalvikExecutableCode: {
|
|
2578
|
+
registersSize,
|
|
2579
|
+
insSize: -1, // TODO
|
|
2580
|
+
outsSize: -1, // TODO
|
|
2581
|
+
debugInfo,
|
|
2582
|
+
instructions: instructions as any, // TODO
|
|
2583
|
+
tries,
|
|
2584
|
+
// _annotations,
|
|
2585
|
+
},
|
|
2586
|
+
parameters,
|
|
2587
|
+
parameterAnnotations: parameters.filter(parameter => parameter.annotation !== undefined),
|
|
2588
|
+
methodAnnotations: annotations,
|
|
2589
|
+
};
|
|
2590
|
+
},
|
|
2591
|
+
);
|
|
2592
|
+
|
|
2593
|
+
setParserName(smaliExecutableCodeParser, 'smaliExecutableCodeParser');
|
|
2594
|
+
|
|
2595
|
+
// TODO: ???
|
|
2596
|
+
const sortRegistersOperations = new Set<DalvikBytecodeOperation['operation']>([
|
|
2597
|
+
'if-eq',
|
|
2598
|
+
'if-ne',
|
|
2599
|
+
]);
|
|
2600
|
+
|
|
2601
|
+
// All 12x format operations need register reversal because:
|
|
2602
|
+
// - Bytecode format is B|A (B in high nibble, A in low nibble)
|
|
2603
|
+
// - nibblesParser returns [B, A]
|
|
2604
|
+
// - Smali syntax is "op vA, vB" (destination first)
|
|
2605
|
+
// - So we need to reverse to get [A, B]
|
|
2606
|
+
const reverseRegistersOperations = new Set<DalvikBytecodeOperation['operation']>([
|
|
2607
|
+
// Move operations
|
|
2608
|
+
'move',
|
|
2609
|
+
'move-wide',
|
|
2610
|
+
'move-object',
|
|
2611
|
+
|
|
2612
|
+
// Unary operations (negation, bitwise not)
|
|
2613
|
+
'neg-int',
|
|
2614
|
+
'not-int',
|
|
2615
|
+
'neg-long',
|
|
2616
|
+
'not-long',
|
|
2617
|
+
'neg-float',
|
|
2618
|
+
'neg-double',
|
|
2619
|
+
|
|
2620
|
+
// Type conversion operations
|
|
2621
|
+
'int-to-long',
|
|
2622
|
+
'int-to-float',
|
|
2623
|
+
'int-to-double',
|
|
2624
|
+
'long-to-int',
|
|
2625
|
+
'long-to-float',
|
|
2626
|
+
'long-to-double',
|
|
2627
|
+
'float-to-int',
|
|
2628
|
+
'float-to-long',
|
|
2629
|
+
'float-to-double',
|
|
2630
|
+
'double-to-int',
|
|
2631
|
+
'double-to-long',
|
|
2632
|
+
'double-to-float',
|
|
2633
|
+
'int-to-byte',
|
|
2634
|
+
'int-to-char',
|
|
2635
|
+
'int-to-short',
|
|
2636
|
+
|
|
2637
|
+
// Binary operations with /2addr suffix
|
|
2638
|
+
'add-int/2addr',
|
|
2639
|
+
'sub-int/2addr',
|
|
2640
|
+
'mul-int/2addr',
|
|
2641
|
+
'div-int/2addr',
|
|
2642
|
+
'rem-int/2addr',
|
|
2643
|
+
'and-int/2addr',
|
|
2644
|
+
'or-int/2addr',
|
|
2645
|
+
'xor-int/2addr',
|
|
2646
|
+
'shl-int/2addr',
|
|
2647
|
+
'shr-int/2addr',
|
|
2648
|
+
'ushr-int/2addr',
|
|
2649
|
+
|
|
2650
|
+
'add-long/2addr',
|
|
2651
|
+
'sub-long/2addr',
|
|
2652
|
+
'mul-long/2addr',
|
|
2653
|
+
'div-long/2addr',
|
|
2654
|
+
'rem-long/2addr',
|
|
2655
|
+
'and-long/2addr',
|
|
2656
|
+
'or-long/2addr',
|
|
2657
|
+
'xor-long/2addr',
|
|
2658
|
+
'shl-long/2addr',
|
|
2659
|
+
'shr-long/2addr',
|
|
2660
|
+
'ushr-long/2addr',
|
|
2661
|
+
|
|
2662
|
+
'add-float/2addr',
|
|
2663
|
+
'sub-float/2addr',
|
|
2664
|
+
'mul-float/2addr',
|
|
2665
|
+
'div-float/2addr',
|
|
2666
|
+
'rem-float/2addr',
|
|
2667
|
+
|
|
2668
|
+
'add-double/2addr',
|
|
2669
|
+
'sub-double/2addr',
|
|
2670
|
+
'mul-double/2addr',
|
|
2671
|
+
'div-double/2addr',
|
|
2672
|
+
'rem-double/2addr',
|
|
2673
|
+
]);
|
|
2674
|
+
|
|
2675
|
+
function normalizeOperation(operation: DalvikBytecodeOperation): DalvikBytecodeOperation {
|
|
2676
|
+
if (
|
|
2677
|
+
sortRegistersOperations.has(operation.operation)
|
|
2678
|
+
&& 'registers' in operation
|
|
2679
|
+
) {
|
|
2680
|
+
operation.registers.sort((a, b) => a - b);
|
|
2681
|
+
}
|
|
2682
|
+
|
|
2683
|
+
if (
|
|
2684
|
+
reverseRegistersOperations.has(operation.operation)
|
|
2685
|
+
&& 'registers' in operation
|
|
2686
|
+
) {
|
|
2687
|
+
operation.registers.reverse();
|
|
2688
|
+
}
|
|
2689
|
+
|
|
2690
|
+
return operation;
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
type SmaliMethod<DalvikBytecode> = {
|
|
2694
|
+
dalvikExecutableMethodWithAccess: DalvikExecutableMethodWithAccess<DalvikBytecode>;
|
|
2695
|
+
parameterAnnotations: SmaliCodeParameter[];
|
|
2696
|
+
methodAnnotations: SmaliAnnotation[];
|
|
2697
|
+
};
|
|
2698
|
+
|
|
2699
|
+
export const smaliMethodParser: Parser<SmaliMethod<DalvikBytecode>, string> = promiseCompose(
|
|
2700
|
+
createTupleParser([
|
|
2701
|
+
createExactSequenceParser('.method '),
|
|
2702
|
+
createOptionalParser(promiseCompose(
|
|
2703
|
+
createTupleParser([
|
|
2704
|
+
smaliAccessFlagsParser,
|
|
2705
|
+
smaliSingleWhitespaceParser,
|
|
2706
|
+
]),
|
|
2707
|
+
([accessFlags]) => accessFlags,
|
|
2708
|
+
)),
|
|
2709
|
+
smaliMemberNameParser,
|
|
2710
|
+
smaliMethodPrototypeParser,
|
|
2711
|
+
smaliLineEndPraser,
|
|
2712
|
+
smaliExecutableCodeParser,
|
|
2713
|
+
createArrayParser(smaliCodeLineParser),
|
|
2714
|
+
createExactSequenceParser('.end method\n'),
|
|
2715
|
+
]),
|
|
2716
|
+
([
|
|
2717
|
+
_method,
|
|
2718
|
+
accessFlagsOrUndefined,
|
|
2719
|
+
name,
|
|
2720
|
+
prototype,
|
|
2721
|
+
_newline,
|
|
2722
|
+
{
|
|
2723
|
+
dalvikExecutableCode,
|
|
2724
|
+
parameters,
|
|
2725
|
+
parameterAnnotations,
|
|
2726
|
+
methodAnnotations,
|
|
2727
|
+
},
|
|
2728
|
+
_lines,
|
|
2729
|
+
_endMethod,
|
|
2730
|
+
]) => {
|
|
2731
|
+
const accessFlags = accessFlagsOrUndefined ?? dalvikExecutableAccessFlagsDefault();
|
|
2732
|
+
let code: DalvikExecutableCode<DalvikBytecode> | undefined = dalvikExecutableCode;
|
|
2733
|
+
|
|
2734
|
+
if (accessFlags.native && code.instructions.length === 0) {
|
|
2735
|
+
code = undefined;
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
if (code) {
|
|
2739
|
+
const insSize = (accessFlags.static ? 0 : 1) + shortyGetInsSize(prototype.shorty);
|
|
2740
|
+
|
|
2741
|
+
code.insSize = insSize;
|
|
2742
|
+
|
|
2743
|
+
for (const operation of code.instructions) {
|
|
2744
|
+
const smaliRegisters = dalvikBytecodeOperationCompanion.getRegisters(operation) as unknown[] as SmaliRegister[]; // TODO
|
|
2745
|
+
|
|
2746
|
+
if (smaliRegisters.length === 0) {
|
|
2747
|
+
continue;
|
|
2748
|
+
}
|
|
2749
|
+
|
|
2750
|
+
const registers: number[] = smaliRegisters.map(({ prefix, index }) => {
|
|
2751
|
+
if (prefix === 'v') {
|
|
2752
|
+
return index;
|
|
2753
|
+
}
|
|
2754
|
+
|
|
2755
|
+
if (prefix === 'p') {
|
|
2756
|
+
return code.registersSize - insSize + index;
|
|
2757
|
+
}
|
|
2758
|
+
|
|
2759
|
+
invariant(false, 'Expected prefix to be v or p');
|
|
2760
|
+
});
|
|
2761
|
+
|
|
2762
|
+
(operation as any).registers = registers; // TODO
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
let outsSize = 0;
|
|
2766
|
+
|
|
2767
|
+
for (const operation of code.instructions) {
|
|
2768
|
+
if (!operation.operation.startsWith('invoke-')) {
|
|
2769
|
+
continue;
|
|
2770
|
+
}
|
|
2771
|
+
|
|
2772
|
+
const registers = dalvikBytecodeOperationCompanion.getRegisters(operation);
|
|
2773
|
+
|
|
2774
|
+
outsSize = Math.max(outsSize, registers.length); // TODO?: two words for wide types?
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
code.outsSize = outsSize;
|
|
2778
|
+
|
|
2779
|
+
// Populate debug info parameter names
|
|
2780
|
+
// The parameter count is insSize minus 1 for non-static methods (to exclude 'this')
|
|
2781
|
+
if (code.debugInfo) {
|
|
2782
|
+
const paramCount = accessFlags.static ? insSize : insSize - 1;
|
|
2783
|
+
const paramNames: Array<undefined | string> = Array(paramCount).fill(undefined);
|
|
2784
|
+
|
|
2785
|
+
// Map parameter names from .param directives
|
|
2786
|
+
// For non-static methods: p0 is 'this', p1 is first param (index 0)
|
|
2787
|
+
// For static methods: p0 is first param (index 0)
|
|
2788
|
+
for (const param of parameters) {
|
|
2789
|
+
if (param.register.prefix === 'p' && param.name) {
|
|
2790
|
+
const pRegister = param.register.index;
|
|
2791
|
+
// Adjust for 'this' parameter in non-static methods
|
|
2792
|
+
const paramIndex = accessFlags.static ? pRegister : pRegister - 1;
|
|
2793
|
+
if (paramIndex >= 0 && paramIndex < paramCount) {
|
|
2794
|
+
paramNames[paramIndex] = param.name;
|
|
2795
|
+
}
|
|
2796
|
+
}
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
code.debugInfo.parameterNames = paramNames;
|
|
2800
|
+
}
|
|
2801
|
+
}
|
|
2802
|
+
|
|
2803
|
+
return {
|
|
2804
|
+
dalvikExecutableMethodWithAccess: {
|
|
2805
|
+
accessFlags,
|
|
2806
|
+
method: {
|
|
2807
|
+
class: 'FILLED_LATER',
|
|
2808
|
+
prototype,
|
|
2809
|
+
name,
|
|
2810
|
+
},
|
|
2811
|
+
code: code?.instructions.length
|
|
2812
|
+
? {
|
|
2813
|
+
...code,
|
|
2814
|
+
instructions: code.instructions.map(normalizeOperation),
|
|
2815
|
+
}
|
|
2816
|
+
: undefined,
|
|
2817
|
+
},
|
|
2818
|
+
parameterAnnotations,
|
|
2819
|
+
methodAnnotations,
|
|
2820
|
+
};
|
|
2821
|
+
},
|
|
2822
|
+
);
|
|
2823
|
+
|
|
2824
|
+
setParserName(smaliMethodParser, 'smaliMethodParser');
|
|
2825
|
+
|
|
2826
|
+
type SmaliMethods = Pick<DalvikExecutableClassData<DalvikBytecode>, 'directMethods' | 'virtualMethods'> & {
|
|
2827
|
+
parameterAnnotations: DalvikExecutableClassParameterAnnotation[];
|
|
2828
|
+
methodAnnotations: DalvikExecutableClassMethodAnnotation[];
|
|
2829
|
+
};
|
|
2830
|
+
|
|
2831
|
+
const smaliMethodsParser: Parser<SmaliMethods, string> = promiseCompose(
|
|
2832
|
+
createArrayParser<string[] | SmaliMethod<DalvikBytecode>, string>(createDisjunctionParser<string[] | SmaliMethod<DalvikBytecode>, string, string>([
|
|
2833
|
+
smaliMethodParser,
|
|
2834
|
+
smaliCommentsOrNewlinesParser,
|
|
2835
|
+
])),
|
|
2836
|
+
methodsAndComments => {
|
|
2837
|
+
let type: 'directMethod' | 'virtualMethod' = 'directMethod';
|
|
2838
|
+
|
|
2839
|
+
const directMethods: Array<DalvikExecutableMethodWithAccess<DalvikBytecode>> = [];
|
|
2840
|
+
const virtualMethods: Array<DalvikExecutableMethodWithAccess<DalvikBytecode>> = [];
|
|
2841
|
+
|
|
2842
|
+
const parameterAnnotations: DalvikExecutableClassParameterAnnotation[] = [];
|
|
2843
|
+
const methodAnnotations: DalvikExecutableClassMethodAnnotation[] = [];
|
|
2844
|
+
|
|
2845
|
+
function pushParameterAnnotation(annotation: DalvikExecutableClassParameterAnnotation) {
|
|
2846
|
+
if (annotation.annotations.length === 0) {
|
|
2847
|
+
return;
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
const existingMethod = parameterAnnotations.find(parameterAnnotation => dalvikExecutableMethodEquals(
|
|
2851
|
+
parameterAnnotation.method,
|
|
2852
|
+
annotation.method,
|
|
2853
|
+
));
|
|
2854
|
+
|
|
2855
|
+
if (existingMethod) {
|
|
2856
|
+
for (const [ index, parameterAnnotations_ ] of annotation.annotations.entries()) {
|
|
2857
|
+
const existingParameterAnnotations = existingMethod.annotations.at(index);
|
|
2858
|
+
|
|
2859
|
+
if (existingParameterAnnotations) {
|
|
2860
|
+
existingParameterAnnotations.push(...parameterAnnotations_);
|
|
2861
|
+
} else {
|
|
2862
|
+
existingMethod.annotations[index] = parameterAnnotations_;
|
|
2863
|
+
}
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
return;
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2869
|
+
parameterAnnotations.push(annotation);
|
|
2870
|
+
}
|
|
2871
|
+
|
|
2872
|
+
for (const methodOrComment of methodsAndComments) {
|
|
2873
|
+
if (Array.isArray(methodOrComment)) {
|
|
2874
|
+
for (const comment of methodOrComment) {
|
|
2875
|
+
if (comment === ' direct methods') {
|
|
2876
|
+
type = 'directMethod';
|
|
2877
|
+
}
|
|
2878
|
+
|
|
2879
|
+
if (comment === ' virtual methods') {
|
|
2880
|
+
type = 'virtualMethod';
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2884
|
+
continue;
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
invariant(typeof methodOrComment === 'object', 'Expected method or comment');
|
|
2888
|
+
|
|
2889
|
+
const method = methodOrComment;
|
|
2890
|
+
|
|
2891
|
+
if (method.methodAnnotations.length > 0) {
|
|
2892
|
+
methodAnnotations.push({
|
|
2893
|
+
method: method.dalvikExecutableMethodWithAccess.method,
|
|
2894
|
+
annotations: method.methodAnnotations.map(annotation => ({
|
|
2895
|
+
type: annotation.type,
|
|
2896
|
+
visibility: annotation.visibility,
|
|
2897
|
+
elements: annotation.elements.map(element => ({
|
|
2898
|
+
name: element.name,
|
|
2899
|
+
value: convertToTaggedEncodedValue(element.value),
|
|
2900
|
+
})),
|
|
2901
|
+
})),
|
|
2902
|
+
});
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
// Create an annotations array for all parameters, not just those with annotations
|
|
2906
|
+
// In smali, instance methods have p0 as 'this', p1 as first param, etc.
|
|
2907
|
+
// But DEX parameter annotations only include the actual parameters (not 'this')
|
|
2908
|
+
const isStatic = method.dalvikExecutableMethodWithAccess.accessFlags.static;
|
|
2909
|
+
const smaliRegisterOffset = isStatic ? 0 : 1; // P0 is 'this' for instance methods
|
|
2910
|
+
|
|
2911
|
+
const allParameterAnnotations = method.dalvikExecutableMethodWithAccess.method.prototype.parameters.map((_, parameterIndex) => {
|
|
2912
|
+
const smaliRegisterIndex = parameterIndex + smaliRegisterOffset;
|
|
2913
|
+
const parameterAnnotation = method.parameterAnnotations.find(pa => pa.register.prefix === 'p' && pa.register.index === smaliRegisterIndex);
|
|
2914
|
+
|
|
2915
|
+
if (parameterAnnotation?.annotation) {
|
|
2916
|
+
return [ {
|
|
2917
|
+
type: parameterAnnotation.annotation.type,
|
|
2918
|
+
visibility: parameterAnnotation.annotation.visibility,
|
|
2919
|
+
elements: parameterAnnotation.annotation.elements.map(element => ({
|
|
2920
|
+
name: element.name,
|
|
2921
|
+
value: convertToTaggedEncodedValue(element.value),
|
|
2922
|
+
})),
|
|
2923
|
+
} ];
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
return [];
|
|
2927
|
+
});
|
|
2928
|
+
|
|
2929
|
+
// Only push parameter annotations if there are some actual annotations
|
|
2930
|
+
if (allParameterAnnotations.some(annotations => annotations.length > 0)) {
|
|
2931
|
+
pushParameterAnnotation({
|
|
2932
|
+
method: method.dalvikExecutableMethodWithAccess.method,
|
|
2933
|
+
annotations: allParameterAnnotations,
|
|
2934
|
+
});
|
|
2935
|
+
}
|
|
2936
|
+
|
|
2937
|
+
if (type === 'directMethod') {
|
|
2938
|
+
directMethods.push(method.dalvikExecutableMethodWithAccess);
|
|
2939
|
+
|
|
2940
|
+
continue;
|
|
2941
|
+
}
|
|
2942
|
+
|
|
2943
|
+
if (type === 'virtualMethod') {
|
|
2944
|
+
virtualMethods.push(method.dalvikExecutableMethodWithAccess);
|
|
2945
|
+
|
|
2946
|
+
continue;
|
|
2947
|
+
}
|
|
2948
|
+
|
|
2949
|
+
invariant(false, 'Expected method type');
|
|
2950
|
+
}
|
|
2951
|
+
|
|
2952
|
+
// Sort parameter annotations by method index in the combined method list
|
|
2953
|
+
// to match the order in the DEX file's annotations directory
|
|
2954
|
+
const allMethods = [...directMethods, ...virtualMethods];
|
|
2955
|
+
parameterAnnotations.sort((a, b) => {
|
|
2956
|
+
const indexA = allMethods.findIndex(m => dalvikExecutableMethodEquals(m.method, a.method));
|
|
2957
|
+
const indexB = allMethods.findIndex(m => dalvikExecutableMethodEquals(m.method, b.method));
|
|
2958
|
+
return indexA - indexB;
|
|
2959
|
+
});
|
|
2960
|
+
|
|
2961
|
+
return {
|
|
2962
|
+
directMethods,
|
|
2963
|
+
virtualMethods,
|
|
2964
|
+
parameterAnnotations,
|
|
2965
|
+
methodAnnotations,
|
|
2966
|
+
};
|
|
2967
|
+
},
|
|
2968
|
+
);
|
|
2969
|
+
|
|
2970
|
+
setParserName(smaliMethodsParser, 'smaliMethodsParser');
|
|
2971
|
+
|
|
2972
|
+
export const smaliParser: Parser<DalvikExecutableClassDefinition<DalvikBytecode>, string> = promiseCompose(
|
|
2973
|
+
createTupleParser([
|
|
2974
|
+
smaliClassDeclarationParser,
|
|
2975
|
+
smaliSuperDeclarationParser,
|
|
2976
|
+
createOptionalParser(smaliSourceDeclarationParser),
|
|
2977
|
+
createOptionalParser(promiseCompose(
|
|
2978
|
+
createTupleParser([
|
|
2979
|
+
smaliCommentsOrNewlinesParser,
|
|
2980
|
+
createNonEmptyArrayParser(smaliInterfaceDeclarationParser),
|
|
2981
|
+
]),
|
|
2982
|
+
([
|
|
2983
|
+
_commentsOrNewlines,
|
|
2984
|
+
interfaces,
|
|
2985
|
+
]) => interfaces,
|
|
2986
|
+
)),
|
|
2987
|
+
createOptionalParser(promiseCompose(
|
|
2988
|
+
createTupleParser([
|
|
2989
|
+
smaliCommentsOrNewlinesParser,
|
|
2990
|
+
createSeparatedNonEmptyArrayParser<SmaliAnnotation, string>(
|
|
2991
|
+
smaliAnnotationParser,
|
|
2992
|
+
smaliCommentsOrNewlinesParser,
|
|
2993
|
+
),
|
|
2994
|
+
]),
|
|
2995
|
+
([
|
|
2996
|
+
_commentsOrNewlines,
|
|
2997
|
+
classAnnotations,
|
|
2998
|
+
]) => classAnnotations,
|
|
2999
|
+
)),
|
|
3000
|
+
createOptionalParser(promiseCompose(
|
|
3001
|
+
createTupleParser([
|
|
3002
|
+
smaliCommentsOrNewlinesParser,
|
|
3003
|
+
smaliFieldsParser,
|
|
3004
|
+
]),
|
|
3005
|
+
([
|
|
3006
|
+
_commentsOrNewlines,
|
|
3007
|
+
fields,
|
|
3008
|
+
]) => fields,
|
|
3009
|
+
)),
|
|
3010
|
+
smaliMethodsParser,
|
|
3011
|
+
]),
|
|
3012
|
+
([
|
|
3013
|
+
{
|
|
3014
|
+
accessFlags,
|
|
3015
|
+
class: class_,
|
|
3016
|
+
},
|
|
3017
|
+
{
|
|
3018
|
+
superclass,
|
|
3019
|
+
},
|
|
3020
|
+
sourceFileObject,
|
|
3021
|
+
interfaces,
|
|
3022
|
+
classAnnotations,
|
|
3023
|
+
smaliFields,
|
|
3024
|
+
methods,
|
|
3025
|
+
]) => {
|
|
3026
|
+
const sourceFile = sourceFileObject?.sourceFile;
|
|
3027
|
+
// Create staticValues array matching DEX format:
|
|
3028
|
+
// - Find the last static field with a non-default initializer
|
|
3029
|
+
// - Create array up to that index with values/nulls
|
|
3030
|
+
// - Fields after the last initializer are not included
|
|
3031
|
+
// - Default values (false, null) are not considered initializers
|
|
3032
|
+
const staticFieldsList = smaliFields?.staticFields ?? [];
|
|
3033
|
+
let lastIndexWithInitializer = -1;
|
|
3034
|
+
for (let i = staticFieldsList.length - 1; i >= 0; i--) {
|
|
3035
|
+
const initValue = staticFieldsList[i].initialValue;
|
|
3036
|
+
// Only consider non-default values as initializers
|
|
3037
|
+
// Numbers, bigints, strings, and true are non-default
|
|
3038
|
+
// false and null are default values
|
|
3039
|
+
if (initValue !== undefined && typeof initValue === 'number') {
|
|
3040
|
+
lastIndexWithInitializer = i;
|
|
3041
|
+
break;
|
|
3042
|
+
}
|
|
3043
|
+
|
|
3044
|
+
if (initValue !== undefined && typeof initValue === 'bigint') {
|
|
3045
|
+
lastIndexWithInitializer = i;
|
|
3046
|
+
break;
|
|
3047
|
+
}
|
|
3048
|
+
|
|
3049
|
+
if (initValue !== undefined && typeof initValue === 'string') {
|
|
3050
|
+
lastIndexWithInitializer = i;
|
|
3051
|
+
break;
|
|
3052
|
+
}
|
|
3053
|
+
|
|
3054
|
+
if (initValue === true) {
|
|
3055
|
+
lastIndexWithInitializer = i;
|
|
3056
|
+
break;
|
|
3057
|
+
}
|
|
3058
|
+
}
|
|
3059
|
+
|
|
3060
|
+
const staticValues = lastIndexWithInitializer === -1
|
|
3061
|
+
? []
|
|
3062
|
+
: staticFieldsList
|
|
3063
|
+
.slice(0, lastIndexWithInitializer + 1)
|
|
3064
|
+
.map(smaliField => {
|
|
3065
|
+
const fieldType = smaliField.field.field.type;
|
|
3066
|
+
|
|
3067
|
+
if (smaliField.initialValue === undefined) {
|
|
3068
|
+
// For integer types without initializer, DEX stores 0
|
|
3069
|
+
if (fieldType === 'I') {
|
|
3070
|
+
return { type: 'int' as const, value: 0 };
|
|
3071
|
+
}
|
|
3072
|
+
if (fieldType === 'B') {
|
|
3073
|
+
return { type: 'byte' as const, value: 0 };
|
|
3074
|
+
}
|
|
3075
|
+
if (fieldType === 'S') {
|
|
3076
|
+
return { type: 'short' as const, value: 0 };
|
|
3077
|
+
}
|
|
3078
|
+
if (fieldType === 'C') {
|
|
3079
|
+
return { type: 'char' as const, value: 0 };
|
|
3080
|
+
}
|
|
3081
|
+
// For long types without initializer, DEX stores 0n
|
|
3082
|
+
if (fieldType === 'J') {
|
|
3083
|
+
return { type: 'long' as const, value: 0n };
|
|
3084
|
+
}
|
|
3085
|
+
// For float/double types without initializer, DEX stores 0
|
|
3086
|
+
if (fieldType === 'F') {
|
|
3087
|
+
return { type: 'float' as const, value: 0 };
|
|
3088
|
+
}
|
|
3089
|
+
if (fieldType === 'D') {
|
|
3090
|
+
return { type: 'double' as const, value: 0 };
|
|
3091
|
+
}
|
|
3092
|
+
// For boolean types without initializer, DEX stores false
|
|
3093
|
+
if (fieldType === 'Z') {
|
|
3094
|
+
return { type: 'boolean' as const, value: false };
|
|
3095
|
+
}
|
|
3096
|
+
// For other types (reference types, etc.), return null
|
|
3097
|
+
return { type: 'null' as const, value: null };
|
|
3098
|
+
}
|
|
3099
|
+
|
|
3100
|
+
// Numeric values are stored in static values array
|
|
3101
|
+
if (typeof smaliField.initialValue === 'number') {
|
|
3102
|
+
// Convert to BigInt for long (J) types
|
|
3103
|
+
if (fieldType === 'J') {
|
|
3104
|
+
return { type: 'long' as const, value: BigInt(smaliField.initialValue) };
|
|
3105
|
+
}
|
|
3106
|
+
if (fieldType === 'B') {
|
|
3107
|
+
return { type: 'byte' as const, value: smaliField.initialValue };
|
|
3108
|
+
}
|
|
3109
|
+
if (fieldType === 'S') {
|
|
3110
|
+
return { type: 'short' as const, value: smaliField.initialValue };
|
|
3111
|
+
}
|
|
3112
|
+
if (fieldType === 'C') {
|
|
3113
|
+
return { type: 'char' as const, value: smaliField.initialValue };
|
|
3114
|
+
}
|
|
3115
|
+
if (fieldType === 'F') {
|
|
3116
|
+
return { type: 'float' as const, value: smaliField.initialValue };
|
|
3117
|
+
}
|
|
3118
|
+
if (fieldType === 'D') {
|
|
3119
|
+
return { type: 'double' as const, value: smaliField.initialValue };
|
|
3120
|
+
}
|
|
3121
|
+
// Default to int for other numeric types
|
|
3122
|
+
return { type: 'int' as const, value: smaliField.initialValue };
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
// BigInt values for long (J) types
|
|
3126
|
+
if (typeof smaliField.initialValue === 'bigint') {
|
|
3127
|
+
return { type: 'long' as const, value: smaliField.initialValue };
|
|
3128
|
+
}
|
|
3129
|
+
|
|
3130
|
+
// String values should be stored as string type
|
|
3131
|
+
if (typeof smaliField.initialValue === 'string') {
|
|
3132
|
+
return { type: 'string' as const, value: smaliField.initialValue };
|
|
3133
|
+
}
|
|
3134
|
+
|
|
3135
|
+
// Boolean true is a non-default value and should be in staticValues
|
|
3136
|
+
if (smaliField.initialValue === true) {
|
|
3137
|
+
return { type: 'boolean' as const, value: true };
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
// Boolean false and null are default values
|
|
3141
|
+
// For boolean fields with explicit false, return false
|
|
3142
|
+
if (smaliField.initialValue === false && fieldType === 'Z') {
|
|
3143
|
+
return { type: 'boolean' as const, value: false };
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
// For null or other default values, return null
|
|
3147
|
+
return { type: 'null' as const, value: null };
|
|
3148
|
+
});
|
|
3149
|
+
const fields = {
|
|
3150
|
+
staticFields: smaliFields?.staticFields.map(({ field }) => field) ?? [],
|
|
3151
|
+
instanceFields: smaliFields?.instanceFields.map(({ field }) => field) ?? [],
|
|
3152
|
+
};
|
|
3153
|
+
|
|
3154
|
+
// Sort fields to match DEX file order (by field name, then type)
|
|
3155
|
+
// This matches the field order in class_data_item in DEX files
|
|
3156
|
+
// Use binary string comparison (case-sensitive) to match UTF-8 byte order
|
|
3157
|
+
const sortFields = (fieldsList: DalvikExecutableFieldWithAccess[]) => {
|
|
3158
|
+
fieldsList.sort((a, b) => {
|
|
3159
|
+
// First by field name (case-sensitive comparison)
|
|
3160
|
+
if (a.field.name !== b.field.name) {
|
|
3161
|
+
return a.field.name < b.field.name ? -1 : 1;
|
|
3162
|
+
}
|
|
3163
|
+
// Then by field type (case-sensitive comparison)
|
|
3164
|
+
return a.field.type < b.field.type ? -1 : 1;
|
|
3165
|
+
});
|
|
3166
|
+
};
|
|
3167
|
+
|
|
3168
|
+
sortFields(fields.staticFields);
|
|
3169
|
+
sortFields(fields.instanceFields);
|
|
3170
|
+
|
|
3171
|
+
const annotations: DalvikExecutableClassAnnotations = {
|
|
3172
|
+
classAnnotations: (classAnnotations ?? []).map(annotation => ({
|
|
3173
|
+
type: annotation.type,
|
|
3174
|
+
visibility: annotation.visibility,
|
|
3175
|
+
elements: annotation.elements.map(element => ({
|
|
3176
|
+
name: element.name,
|
|
3177
|
+
value: convertToTaggedEncodedValue(element.value),
|
|
3178
|
+
})),
|
|
3179
|
+
})),
|
|
3180
|
+
fieldAnnotations: [],
|
|
3181
|
+
methodAnnotations: methods.methodAnnotations,
|
|
3182
|
+
parameterAnnotations: methods.parameterAnnotations,
|
|
3183
|
+
};
|
|
3184
|
+
|
|
3185
|
+
for (const smaliField of [ ...(smaliFields?.staticFields ?? []), ...(smaliFields?.instanceFields ?? []) ]) {
|
|
3186
|
+
if (smaliField.annotations.length === 0) {
|
|
3187
|
+
continue;
|
|
3188
|
+
}
|
|
3189
|
+
|
|
3190
|
+
const existingFieldAnnotations = annotations.fieldAnnotations.find(fieldAnnotation => dalvikExecutableFieldEquals(
|
|
3191
|
+
fieldAnnotation.field,
|
|
3192
|
+
smaliField.field.field,
|
|
3193
|
+
));
|
|
3194
|
+
|
|
3195
|
+
if (existingFieldAnnotations) {
|
|
3196
|
+
existingFieldAnnotations.annotations ??= [];
|
|
3197
|
+
existingFieldAnnotations.annotations.push(...smaliField.annotations.map(annotation => ({
|
|
3198
|
+
type: annotation.type,
|
|
3199
|
+
visibility: annotation.visibility,
|
|
3200
|
+
elements: annotation.elements.map(element => ({
|
|
3201
|
+
name: element.name,
|
|
3202
|
+
value: convertToTaggedEncodedValue(element.value),
|
|
3203
|
+
})),
|
|
3204
|
+
})));
|
|
3205
|
+
continue;
|
|
3206
|
+
}
|
|
3207
|
+
|
|
3208
|
+
annotations.fieldAnnotations.push({
|
|
3209
|
+
field: smaliField.field.field,
|
|
3210
|
+
annotations: smaliField.annotations.map(annotation => ({
|
|
3211
|
+
type: annotation.type,
|
|
3212
|
+
visibility: annotation.visibility,
|
|
3213
|
+
elements: annotation.elements.map(element => ({
|
|
3214
|
+
name: element.name,
|
|
3215
|
+
value: convertToTaggedEncodedValue(element.value),
|
|
3216
|
+
})),
|
|
3217
|
+
})),
|
|
3218
|
+
});
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
// Sort field annotations to match DEX file order (by field name, then type)
|
|
3222
|
+
// This matches the annotations_directory_item field_annotations order in DEX files
|
|
3223
|
+
// Use binary string comparison (case-sensitive) to match UTF-8 byte order
|
|
3224
|
+
annotations.fieldAnnotations.sort((a, b) => {
|
|
3225
|
+
// First by field name (case-sensitive comparison)
|
|
3226
|
+
if (a.field.name !== b.field.name) {
|
|
3227
|
+
return a.field.name < b.field.name ? -1 : 1;
|
|
3228
|
+
}
|
|
3229
|
+
// Then by field type (case-sensitive comparison)
|
|
3230
|
+
return a.field.type < b.field.type ? -1 : 1;
|
|
3231
|
+
});
|
|
3232
|
+
|
|
3233
|
+
// Compute synthetic flag from members if not explicitly set
|
|
3234
|
+
// This matches baksmali behavior where synthetic flag at class level is not output
|
|
3235
|
+
// but can be inferred from all members being synthetic
|
|
3236
|
+
const allMembers = [
|
|
3237
|
+
...fields.staticFields,
|
|
3238
|
+
...fields.instanceFields,
|
|
3239
|
+
...methods.directMethods,
|
|
3240
|
+
// Note: virtualMethods are not included, matching DEX parser behavior
|
|
3241
|
+
];
|
|
3242
|
+
|
|
3243
|
+
const allMembersAreSynthetic = (
|
|
3244
|
+
allMembers.every(member => member.accessFlags.synthetic)
|
|
3245
|
+
&& allMembers.length > 0
|
|
3246
|
+
);
|
|
3247
|
+
|
|
3248
|
+
const finalAccessFlags = {
|
|
3249
|
+
...accessFlags,
|
|
3250
|
+
// Use the synthetic flag from the class declaration, or compute it from members if not set
|
|
3251
|
+
synthetic: accessFlags.synthetic || allMembersAreSynthetic,
|
|
3252
|
+
};
|
|
3253
|
+
|
|
3254
|
+
return {
|
|
3255
|
+
accessFlags: finalAccessFlags,
|
|
3256
|
+
class: class_,
|
|
3257
|
+
superclass,
|
|
3258
|
+
sourceFile,
|
|
3259
|
+
annotations: (
|
|
3260
|
+
(
|
|
3261
|
+
annotations.classAnnotations?.length
|
|
3262
|
+
|| annotations.fieldAnnotations?.length
|
|
3263
|
+
|| annotations.methodAnnotations?.length
|
|
3264
|
+
|| annotations.parameterAnnotations?.length
|
|
3265
|
+
)
|
|
3266
|
+
? ({
|
|
3267
|
+
classAnnotations: annotations.classAnnotations,
|
|
3268
|
+
fieldAnnotations: annotations.fieldAnnotations.map(fieldAnnotation => ({
|
|
3269
|
+
...fieldAnnotation,
|
|
3270
|
+
field: {
|
|
3271
|
+
...fieldAnnotation.field,
|
|
3272
|
+
class: class_,
|
|
3273
|
+
},
|
|
3274
|
+
})),
|
|
3275
|
+
methodAnnotations: annotations.methodAnnotations
|
|
3276
|
+
.map(methodAnnotation => ({
|
|
3277
|
+
...methodAnnotation,
|
|
3278
|
+
method: {
|
|
3279
|
+
...methodAnnotation.method,
|
|
3280
|
+
class: class_,
|
|
3281
|
+
},
|
|
3282
|
+
}))
|
|
3283
|
+
// Sort method annotations to match DEX file order (lexicographic by method name, then prototype shorty)
|
|
3284
|
+
// This matches the method_idx order in the DEX file's method_id table
|
|
3285
|
+
.sort((a, b) => {
|
|
3286
|
+
// Sort by method name first (using code point comparison, not locale-aware)
|
|
3287
|
+
if (a.method.name !== b.method.name) {
|
|
3288
|
+
return a.method.name < b.method.name ? -1 : 1;
|
|
3289
|
+
}
|
|
3290
|
+
// Then by shorty (prototype signature)
|
|
3291
|
+
return a.method.prototype.shorty < b.method.prototype.shorty ? -1 : 1;
|
|
3292
|
+
}),
|
|
3293
|
+
parameterAnnotations: annotations.parameterAnnotations.map(parameterAnnotation => ({
|
|
3294
|
+
...parameterAnnotation,
|
|
3295
|
+
method: {
|
|
3296
|
+
...parameterAnnotation.method,
|
|
3297
|
+
class: class_,
|
|
3298
|
+
},
|
|
3299
|
+
})),
|
|
3300
|
+
})
|
|
3301
|
+
: undefined
|
|
3302
|
+
),
|
|
3303
|
+
classData: (
|
|
3304
|
+
(
|
|
3305
|
+
methods.directMethods.length > 0
|
|
3306
|
+
|| methods.virtualMethods.length > 0
|
|
3307
|
+
|| (fields?.staticFields.length ?? 0)
|
|
3308
|
+
|| (fields?.instanceFields.length ?? 0)
|
|
3309
|
+
)
|
|
3310
|
+
? ({
|
|
3311
|
+
directMethods: methods.directMethods.map(method => ({
|
|
3312
|
+
...method,
|
|
3313
|
+
method: {
|
|
3314
|
+
...method.method,
|
|
3315
|
+
class: class_,
|
|
3316
|
+
},
|
|
3317
|
+
})),
|
|
3318
|
+
virtualMethods: methods.virtualMethods.map(method => ({
|
|
3319
|
+
...method,
|
|
3320
|
+
method: {
|
|
3321
|
+
...method.method,
|
|
3322
|
+
class: class_,
|
|
3323
|
+
},
|
|
3324
|
+
})),
|
|
3325
|
+
staticFields: (fields?.staticFields ?? []).map(field => ({
|
|
3326
|
+
...field,
|
|
3327
|
+
field: {
|
|
3328
|
+
...field.field,
|
|
3329
|
+
class: class_,
|
|
3330
|
+
},
|
|
3331
|
+
})),
|
|
3332
|
+
instanceFields: (fields?.instanceFields ?? []).map(field => ({
|
|
3333
|
+
...field,
|
|
3334
|
+
field: {
|
|
3335
|
+
...field.field,
|
|
3336
|
+
class: class_,
|
|
3337
|
+
},
|
|
3338
|
+
})),
|
|
3339
|
+
})
|
|
3340
|
+
: undefined
|
|
3341
|
+
),
|
|
3342
|
+
interfaces: interfaces ?? [],
|
|
3343
|
+
staticValues,
|
|
3344
|
+
};
|
|
3345
|
+
},
|
|
3346
|
+
);
|
|
3347
|
+
|
|
3348
|
+
setParserName(smaliParser, 'smaliParser');
|