@futpib/parser 1.0.2 → 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 +1 -1
- package/build/allSettledStream.test.js +2 -2
- package/build/androidPackageParser.d.ts +1 -1
- package/build/androidPackageParser.js +5 -3
- package/build/androidPackageParser.test.js +7 -7
- package/build/androidPackageUnparser.d.ts +2 -2
- package/build/androidPackageUnparser.js +18 -14
- package/build/androidPackageUnparser.test.js +7 -7
- 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/arrayUnparser.d.ts +1 -1
- package/build/backsmali.d.ts +3 -1
- package/build/backsmali.js +31 -3
- package/build/customInvariant.d.ts +2 -1
- package/build/customInvariant.js +4 -6
- package/build/dalvikBytecodeParser/formatParsers.d.ts +76 -2
- package/build/dalvikBytecodeParser/formatParsers.js +146 -11
- 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 +1105 -5
- package/build/dalvikBytecodeParser.js +658 -205
- 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.d.ts +1 -0
- package/build/dalvikBytecodeUnparser.test.js +25 -0
- package/build/dalvikExecutable.d.ts +65 -8
- package/build/dalvikExecutable.js +36 -0
- package/build/dalvikExecutableParser/stringSyntaxParser.d.ts +1 -1
- package/build/dalvikExecutableParser/stringSyntaxParser.js +17 -17
- package/build/dalvikExecutableParser/typeParsers.d.ts +2 -1
- package/build/dalvikExecutableParser/typeParsers.js +16 -11
- package/build/dalvikExecutableParser/typedNumbers.d.ts +85 -69
- package/build/dalvikExecutableParser/typedNumbers.js +0 -1
- package/build/dalvikExecutableParser.d.ts +2 -2
- package/build/dalvikExecutableParser.js +655 -337
- package/build/dalvikExecutableParser.test.js +24 -22
- package/build/dalvikExecutableParserAgainstSmaliParser.test.js +223 -246
- 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.d.ts +1 -0
- package/build/dalvikExecutableUnparser.test.js +31 -0
- package/build/debugLogInputParser.js +1 -1
- package/build/disjunctionParser.d.ts +2 -2
- package/build/disjunctionParser.js +2 -2
- package/build/elementTerminatedArrayParser.d.ts +2 -2
- package/build/elementTerminatedArrayParser.js +1 -1
- package/build/elementTerminatedArrayParser.test.js +5 -5
- package/build/elementTerminatedSequenceArrayParser.d.ts +2 -2
- package/build/elementTerminatedSequenceArrayParser.js +1 -1
- package/build/elementTerminatedSequenceArrayParser.test.js +2 -2
- package/build/elementTerminatedSequenceParser.d.ts +2 -2
- package/build/elementTerminatedSequenceParser.js +1 -1
- package/build/elementTerminatedSequenceParser.test.js +2 -2
- package/build/endOfInputParser.d.ts +1 -1
- package/build/exactElementSwitchParser.d.ts +3 -0
- package/build/exactElementSwitchParser.js +22 -0
- package/build/fetchCid.js +2 -6
- package/build/fetchCid.test.d.ts +1 -0
- package/build/fetchCid.test.js +16 -0
- package/build/fixedLengthSequenceParser.test.js +2 -2
- package/build/hasExecutable.js +2 -2
- package/build/highResolutionTimer.js +1 -1
- package/build/inputReader.d.ts +1 -1
- package/build/inputReader.test.js +33 -45
- package/build/javaKeyStoreParser.test.js +6 -6
- package/build/jsonParser.js +8 -8
- 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 +1 -1
- package/build/leb128Parser.js +10 -10
- package/build/leb128Parser.test.js +7 -7
- package/build/negativeLookaheadParser.js +2 -2
- package/build/negativeLookaheadParser.test.js +4 -4
- 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.js +2 -2
- package/build/nonEmptyArrayParser.test.js +2 -1
- package/build/optionalParser.js +2 -2
- package/build/parser.d.ts +2 -1
- package/build/parser.js +23 -8
- package/build/parser.test.js +78 -29
- package/build/parserConsumedSequenceParser.d.ts +1 -1
- package/build/parserConsumedSequenceParser.js +2 -2
- package/build/parserContext.d.ts +8 -6
- package/build/parserContext.js +60 -33
- package/build/parserContext.test.js +7 -3
- package/build/parserError.d.ts +603 -44
- package/build/parserError.js +98 -53
- package/build/parserImplementationInvariant.d.ts +1 -1
- package/build/parserImplementationInvariant.js +2 -2
- package/build/parserInputCompanion.js +2 -2
- package/build/promiseCompose.js +1 -2
- package/build/separatedArrayParser.js +2 -2
- 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.js +1 -1
- package/build/sequenceTerminatedSequenceParser.d.ts +2 -2
- package/build/sequenceTerminatedSequenceParser.js +3 -3
- package/build/sequenceTerminatedSequenceParser.test.js +1 -1
- package/build/sequenceUnparser.d.ts +1 -1
- package/build/skipToParser.d.ts +1 -1
- package/build/skipToParser.js +2 -2
- package/build/sliceBoundedParser.test.js +4 -9
- package/build/smali.d.ts +1 -1
- package/build/smali.js +6 -2
- package/build/smaliParser.d.ts +62 -6
- package/build/smaliParser.js +1721 -296
- package/build/smaliParser.test.js +338 -43
- package/build/stringFromAsyncIterable.d.ts +1 -0
- package/build/stringFromAsyncIterable.js +7 -0
- package/build/terminatedArrayParser.js +4 -4
- package/build/terminatedArrayParser.test.js +7 -7
- package/build/toAsyncIterator.js +4 -4
- package/build/unionParser.d.ts +1 -1
- package/build/unionParser.js +2 -2
- package/build/unionParser.test.js +3 -3
- 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.js +1 -1
- package/build/zipUnparser.d.ts +3 -3
- package/build/zipUnparser.js +9 -19
- package/build/zipUnparser.test.js +1 -1
- package/package.json +19 -26
- package/src/allSettledStream.test.ts +2 -2
- package/src/allSettledStream.ts +3 -3
- package/src/androidPackageParser.test.ts +17 -19
- package/src/androidPackageParser.ts +129 -171
- package/src/androidPackageUnparser.test.ts +19 -21
- package/src/androidPackageUnparser.ts +23 -17
- package/src/arbitrarilySlicedAsyncInterable.ts +1 -1
- 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.ts +2 -2
- package/src/arrayUnparser.ts +2 -2
- package/src/backsmali.ts +48 -4
- package/src/bsonParser.test.ts +12 -14
- package/src/customInvariant.ts +8 -12
- package/src/dalvikBytecodeParser/formatParsers.ts +376 -17
- package/src/dalvikBytecodeParser/formatSizes.ts +35 -0
- package/src/dalvikBytecodeParser/operationFormats.ts +226 -0
- package/src/dalvikBytecodeParser.ts +1042 -243
- package/src/dalvikBytecodeUnparser/formatUnparsers.ts +442 -0
- package/src/dalvikBytecodeUnparser.test.ts +44 -0
- package/src/dalvikBytecodeUnparser.ts +758 -0
- package/src/dalvikExecutable.ts +110 -48
- package/src/dalvikExecutableParser/stringSyntaxParser.ts +33 -33
- package/src/dalvikExecutableParser/typeParsers.ts +23 -14
- package/src/dalvikExecutableParser/typedNumbers.ts +19 -19
- package/src/dalvikExecutableParser.test.ts +60 -60
- package/src/dalvikExecutableParser.test.ts.md +6 -6
- package/src/dalvikExecutableParser.test.ts.snap +0 -0
- package/src/dalvikExecutableParser.ts +911 -434
- package/src/dalvikExecutableParserAgainstSmaliParser.test.ts +256 -239
- 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 +1 -1
- package/src/disjunctionParser.ts +5 -5
- package/src/elementTerminatedArrayParser.test.ts +8 -8
- package/src/elementTerminatedArrayParser.ts +2 -2
- package/src/elementTerminatedSequenceArrayParser.test.ts +4 -6
- package/src/elementTerminatedSequenceArrayParser.ts +2 -2
- package/src/elementTerminatedSequenceParser.test.ts +4 -6
- package/src/elementTerminatedSequenceParser.ts +2 -2
- package/src/endOfInputParser.ts +1 -1
- package/src/exactElementSwitchParser.ts +41 -0
- package/src/fetchCid.test.ts +20 -0
- package/src/fetchCid.ts +3 -7
- package/src/fixedLengthSequenceParser.test.ts +10 -12
- package/src/hasExecutable.ts +2 -2
- package/src/highResolutionTimer.ts +1 -1
- package/src/inputReader.test.ts +39 -52
- package/src/inputReader.ts +2 -4
- package/src/inputReaderState.ts +1 -1
- package/src/inspect.ts +1 -1
- package/src/javaKeyStoreParser.test.ts +12 -14
- package/src/javaKeyStoreParser.ts +2 -6
- package/src/jsonParser.test.ts +2 -4
- package/src/jsonParser.ts +34 -38
- package/src/lazyMessageError.test.ts +21 -0
- package/src/lazyMessageError.ts +88 -0
- package/src/leb128Parser.test.ts +25 -23
- package/src/leb128Parser.ts +19 -19
- package/src/negativeLookaheadParser.test.ts +7 -11
- package/src/negativeLookaheadParser.ts +2 -2
- package/src/noStackCaptureOverheadError.test.ts +17 -0
- package/src/noStackCaptureOverheadError.ts +12 -0
- package/src/nonEmptyArrayParser.test.ts +3 -2
- package/src/nonEmptyArrayParser.ts +2 -2
- package/src/optionalParser.ts +2 -2
- package/src/parser.test.ts +96 -43
- package/src/parser.test.ts.md +13 -6
- package/src/parser.test.ts.snap +0 -0
- package/src/parser.ts +35 -12
- package/src/parserAccessorParser.ts +1 -1
- package/src/parserConsumedSequenceParser.ts +3 -3
- package/src/parserContext.test.ts +7 -3
- package/src/parserContext.ts +82 -48
- package/src/parserError.ts +143 -63
- package/src/parserImplementationInvariant.ts +3 -3
- package/src/parserInputCompanion.ts +2 -2
- package/src/promiseCompose.ts +2 -2
- package/src/separatedArrayParser.ts +3 -3
- package/src/separatedNonEmptyArrayParser.test.ts +117 -0
- package/src/separatedNonEmptyArrayParser.ts +61 -0
- package/src/sequenceBuffer.test.ts +9 -9
- package/src/sequenceBuffer.ts +4 -4
- package/src/sequenceTerminatedSequenceParser.test.ts +3 -5
- package/src/sequenceTerminatedSequenceParser.ts +4 -4
- package/src/sequenceUnparser.ts +2 -2
- package/src/skipToParser.ts +2 -2
- package/src/sliceBoundedParser.test.ts +4 -12
- package/src/sliceBoundedParser.ts +2 -2
- package/src/smali.ts +8 -3
- package/src/smaliParser.test.ts +377 -66
- package/src/smaliParser.test.ts.md +1635 -48
- package/src/smaliParser.test.ts.snap +0 -0
- package/src/smaliParser.ts +2751 -569
- package/src/stringFromAsyncIterable.ts +9 -0
- package/src/terminatedArrayParser.test.ts +11 -11
- package/src/terminatedArrayParser.ts +5 -7
- package/src/toAsyncIterator.ts +8 -8
- package/src/uint8Array.ts +2 -3
- package/src/unionParser.test.ts +22 -23
- package/src/unionParser.ts +6 -8
- 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 +10 -18
- package/src/zipUnparser.test.ts +1 -1
- package/src/zipUnparser.ts +52 -64
- package/tsconfig.json +7 -1
- package/xo.config.ts +15 -0
- package/.yarn/releases/yarn-4.5.3.cjs +0 -934
package/build/smaliParser.js
CHANGED
|
@@ -1,22 +1,145 @@
|
|
|
1
|
-
import invariant from
|
|
2
|
-
import { dalvikBytecodeOperationCompanion } from
|
|
3
|
-
import { isDalvikExecutableField, isDalvikExecutableMethod } from
|
|
4
|
-
import { createExactSequenceParser } from
|
|
5
|
-
import { cloneParser, setParserName } from
|
|
6
|
-
import { promiseCompose } from
|
|
7
|
-
import { createTupleParser } from
|
|
8
|
-
import { createUnionParser } from
|
|
9
|
-
import { createArrayParser } from
|
|
10
|
-
import { jsonNumberParser, jsonStringParser } from
|
|
11
|
-
import { createNonEmptyArrayParser } from
|
|
12
|
-
import { createOptionalParser } from
|
|
13
|
-
import { createNegativeLookaheadParser } from
|
|
14
|
-
import { createSeparatedArrayParser } from
|
|
15
|
-
import { smaliMemberNameParser, smaliTypeDescriptorParser } from
|
|
16
|
-
import { createDisjunctionParser } from
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
1
|
+
import invariant from 'invariant';
|
|
2
|
+
import { dalvikBytecodeOperationCompanion } from './dalvikBytecodeParser.js';
|
|
3
|
+
import { dalvikExecutableAccessFlagsDefault, dalvikExecutableFieldEquals, dalvikExecutableMethodEquals, isDalvikExecutableField, isDalvikExecutableMethod, } from './dalvikExecutable.js';
|
|
4
|
+
import { createExactSequenceParser } from './exactSequenceParser.js';
|
|
5
|
+
import { cloneParser, setParserName } from './parser.js';
|
|
6
|
+
import { promiseCompose } from './promiseCompose.js';
|
|
7
|
+
import { createTupleParser } from './tupleParser.js';
|
|
8
|
+
import { createUnionParser } from './unionParser.js';
|
|
9
|
+
import { createArrayParser } from './arrayParser.js';
|
|
10
|
+
import { jsonNumberParser, jsonStringParser } from './jsonParser.js';
|
|
11
|
+
import { createNonEmptyArrayParser } from './nonEmptyArrayParser.js';
|
|
12
|
+
import { createOptionalParser } from './optionalParser.js';
|
|
13
|
+
import { createNegativeLookaheadParser } from './negativeLookaheadParser.js';
|
|
14
|
+
import { createSeparatedArrayParser } from './separatedArrayParser.js';
|
|
15
|
+
import { smaliMemberNameParser, smaliTypeDescriptorParser } from './dalvikExecutableParser/stringSyntaxParser.js';
|
|
16
|
+
import { createDisjunctionParser } from './disjunctionParser.js';
|
|
17
|
+
import { formatSizes } from './dalvikBytecodeParser/formatSizes.js';
|
|
18
|
+
import { operationFormats } from './dalvikBytecodeParser/operationFormats.js';
|
|
19
|
+
import { createSeparatedNonEmptyArrayParser } from './separatedNonEmptyArrayParser.js';
|
|
20
|
+
import { parserCreatorCompose } from './parserCreatorCompose.js';
|
|
21
|
+
import { createElementParser } from './elementParser.js';
|
|
22
|
+
import { createParserAccessorParser } from './parserAccessorParser.js';
|
|
23
|
+
function shortyFromLongy(longy) {
|
|
24
|
+
if (longy.startsWith('[')) {
|
|
25
|
+
return 'L';
|
|
26
|
+
}
|
|
27
|
+
return longy.slice(0, 1);
|
|
28
|
+
}
|
|
29
|
+
function getOperationFormatSize(operation) {
|
|
30
|
+
if (operation.operation === 'packed-switch-payload') {
|
|
31
|
+
return (operation.branchOffsetIndices.length * 2) + 4;
|
|
32
|
+
}
|
|
33
|
+
if (operation.operation === 'sparse-switch-payload') {
|
|
34
|
+
return (operation.branchOffsetIndices.length * 4) + 2;
|
|
35
|
+
}
|
|
36
|
+
if (operation.operation === 'fill-array-data-payload') {
|
|
37
|
+
const dataSize = operation.data.length; // in bytes
|
|
38
|
+
const paddingSize = dataSize % 2; // 1 if odd, 0 if even
|
|
39
|
+
const totalBytes = 8 + dataSize + paddingSize; // header (8 bytes) + data + padding
|
|
40
|
+
return totalBytes / 2; // Convert to code units (1 code unit = 2 bytes)
|
|
41
|
+
}
|
|
42
|
+
const operationFormat = operationFormats[operation.operation];
|
|
43
|
+
invariant(operationFormat, 'Unknown operation format for "%s" (operation: %o)', operation.operation, operation);
|
|
44
|
+
const operationSize = formatSizes[operationFormat];
|
|
45
|
+
invariant(operationSize, 'Unknown operation size for format %s of operation %s', operationFormat, operation.operation);
|
|
46
|
+
return operationSize;
|
|
47
|
+
}
|
|
48
|
+
// Helper function to convert raw annotation element values to tagged encoded values
|
|
49
|
+
function convertToTaggedEncodedValue(wrappedValue) {
|
|
50
|
+
const { kind, value } = wrappedValue;
|
|
51
|
+
// Handle type descriptors
|
|
52
|
+
if (kind === 'type') {
|
|
53
|
+
if (Array.isArray(value)) {
|
|
54
|
+
// Array of type descriptors
|
|
55
|
+
return { type: 'array', value: value.map(v => ({ type: 'type', value: v })) };
|
|
56
|
+
}
|
|
57
|
+
// Single type descriptor
|
|
58
|
+
return { type: 'type', value };
|
|
59
|
+
}
|
|
60
|
+
// Handle string literals
|
|
61
|
+
if (kind === 'string') {
|
|
62
|
+
if (Array.isArray(value)) {
|
|
63
|
+
// Array of strings
|
|
64
|
+
return { type: 'array', value: value.map(v => ({ type: 'string', value: v })) };
|
|
65
|
+
}
|
|
66
|
+
// Single string
|
|
67
|
+
return { type: 'string', value };
|
|
68
|
+
}
|
|
69
|
+
// Handle enum values
|
|
70
|
+
if (kind === 'enum') {
|
|
71
|
+
if (Array.isArray(value)) {
|
|
72
|
+
// Array of enum values
|
|
73
|
+
return { type: 'array', value: value.map(v => ({ type: 'enum', value: v })) };
|
|
74
|
+
}
|
|
75
|
+
// Single enum value
|
|
76
|
+
return { type: 'enum', value };
|
|
77
|
+
}
|
|
78
|
+
// Handle raw values (everything else)
|
|
79
|
+
// Handle null
|
|
80
|
+
if (value === null) {
|
|
81
|
+
return { type: 'null', value: null };
|
|
82
|
+
}
|
|
83
|
+
// Handle boolean
|
|
84
|
+
if (typeof value === 'boolean') {
|
|
85
|
+
return { type: 'boolean', value };
|
|
86
|
+
}
|
|
87
|
+
// Handle numbers - we need to determine the type based on context
|
|
88
|
+
// For annotation elements from smali, we'll use 'int' as the default
|
|
89
|
+
if (typeof value === 'number') {
|
|
90
|
+
// Check if it's a float or integer
|
|
91
|
+
if (!Number.isInteger(value)) {
|
|
92
|
+
return { type: 'float', value };
|
|
93
|
+
}
|
|
94
|
+
// For integers, default to 'int' type
|
|
95
|
+
// The actual byte/short/int distinction would need more context
|
|
96
|
+
return { type: 'int', value };
|
|
97
|
+
}
|
|
98
|
+
// Handle bigint
|
|
99
|
+
if (typeof value === 'bigint') {
|
|
100
|
+
return { type: 'long', value };
|
|
101
|
+
}
|
|
102
|
+
// Handle DalvikExecutableField (including enums)
|
|
103
|
+
if (isDalvikExecutableField(value)) {
|
|
104
|
+
// Note: We can't distinguish between 'field' and 'enum' without more context
|
|
105
|
+
// Default to 'field' type - the context in smali might help distinguish later
|
|
106
|
+
return { type: 'field', value };
|
|
107
|
+
}
|
|
108
|
+
// Handle DalvikExecutableMethod
|
|
109
|
+
if (isDalvikExecutableMethod(value)) {
|
|
110
|
+
return { type: 'method', value };
|
|
111
|
+
}
|
|
112
|
+
// Handle DalvikExecutablePrototype (method type)
|
|
113
|
+
if (typeof value === 'object' && value !== null && 'returnType' in value && 'parameters' in value && 'shorty' in value) {
|
|
114
|
+
return { type: 'methodType', value: value };
|
|
115
|
+
}
|
|
116
|
+
// Handle annotations (subannotations)
|
|
117
|
+
if (typeof value === 'object' && value !== null && 'type' in value && 'elements' in value) {
|
|
118
|
+
const subannotation = value;
|
|
119
|
+
// Convert subannotation to DalvikExecutableAnnotation
|
|
120
|
+
const annotation = {
|
|
121
|
+
type: subannotation.type,
|
|
122
|
+
visibility: 'build', // Subannotations default to 'build' visibility
|
|
123
|
+
elements: subannotation.elements.map(element => ({
|
|
124
|
+
name: element.name,
|
|
125
|
+
value: convertToTaggedEncodedValue(element.value),
|
|
126
|
+
})),
|
|
127
|
+
};
|
|
128
|
+
return { type: 'annotation', value: annotation };
|
|
129
|
+
}
|
|
130
|
+
// Handle arrays
|
|
131
|
+
if (Array.isArray(value)) {
|
|
132
|
+
return { type: 'array', value: value.map(v => convertToTaggedEncodedValue({ kind: 'raw', value: v })) };
|
|
133
|
+
}
|
|
134
|
+
// Fallback - this shouldn't happen in well-formed smali
|
|
135
|
+
throw new Error(`Cannot convert value to tagged encoded value: ${JSON.stringify(value)}`);
|
|
136
|
+
}
|
|
137
|
+
const smaliNewlinesParser = promiseCompose(createNonEmptyArrayParser(createExactSequenceParser('\n')), _newlines => undefined);
|
|
138
|
+
const smaliSingleWhitespaceParser = createExactSequenceParser(' ');
|
|
139
|
+
const smaliWhitespaceParser = promiseCompose(createArrayParser(smaliSingleWhitespaceParser), _indentation => undefined);
|
|
140
|
+
const smaliSingleIndentationParser = createExactSequenceParser(' ');
|
|
141
|
+
const smaliIndentationParser = promiseCompose(createArrayParser(smaliSingleIndentationParser), _indentation => undefined);
|
|
142
|
+
export const smaliCommentParser = promiseCompose(createTupleParser([
|
|
20
143
|
createExactSequenceParser('#'),
|
|
21
144
|
(async (parserContext) => {
|
|
22
145
|
const characters = [];
|
|
@@ -29,15 +152,28 @@ const smaliCommentParser = promiseCompose(createTupleParser([
|
|
|
29
152
|
parserContext.skip(1);
|
|
30
153
|
continue;
|
|
31
154
|
}
|
|
155
|
+
parserContext.skip(1);
|
|
32
156
|
break;
|
|
33
157
|
}
|
|
34
158
|
return characters.join('');
|
|
35
159
|
}),
|
|
36
160
|
]), ([_hash, comment,]) => comment);
|
|
161
|
+
const smaliIndentedCommentParser = promiseCompose(createTupleParser([
|
|
162
|
+
createNonEmptyArrayParser(smaliSingleIndentationParser),
|
|
163
|
+
smaliCommentParser,
|
|
164
|
+
]), ([_indentation, comment,]) => comment);
|
|
37
165
|
const smaliCommentsOrNewlinesParser = promiseCompose(createArrayParser(createUnionParser([
|
|
38
166
|
smaliNewlinesParser,
|
|
167
|
+
smaliIndentedCommentParser,
|
|
39
168
|
smaliCommentParser,
|
|
40
|
-
])),
|
|
169
|
+
])), newlinesOrComments => newlinesOrComments.filter((newlineOrComment) => typeof newlineOrComment === 'string'));
|
|
170
|
+
const smaliLineEndPraser = promiseCompose(createTupleParser([
|
|
171
|
+
createOptionalParser(smaliWhitespaceParser),
|
|
172
|
+
createUnionParser([
|
|
173
|
+
smaliNewlinesParser,
|
|
174
|
+
smaliCommentParser,
|
|
175
|
+
]),
|
|
176
|
+
]), ([_optionalWhitespace, newlineOrComment,]) => newlineOrComment);
|
|
41
177
|
const smaliIdentifierParser = async (parserContext) => {
|
|
42
178
|
const characters = [];
|
|
43
179
|
while (true) {
|
|
@@ -58,54 +194,169 @@ const smaliIdentifierParser = async (parserContext) => {
|
|
|
58
194
|
return characters.join('');
|
|
59
195
|
};
|
|
60
196
|
setParserName(smaliIdentifierParser, 'smaliIdentifierParser');
|
|
61
|
-
const
|
|
197
|
+
const elementParser = createElementParser();
|
|
198
|
+
const smaliHexNumberParser = promiseCompose(createTupleParser([
|
|
199
|
+
createOptionalParser(createExactSequenceParser('-')),
|
|
200
|
+
createExactSequenceParser('0x'),
|
|
201
|
+
createArrayParser(parserCreatorCompose(() => elementParser, character => async (parserContext) => {
|
|
202
|
+
parserContext.invariant(((character >= '0' && character <= '9')
|
|
203
|
+
|| (character >= 'a' && character <= 'f')
|
|
204
|
+
|| (character >= 'A' && character <= 'F')), 'Expected "0" to "9", "a" to "f", "A" to "F", got "%s"', character);
|
|
205
|
+
return character;
|
|
206
|
+
})()),
|
|
207
|
+
createOptionalParser(createExactSequenceParser('L')),
|
|
208
|
+
]), ([optionalMinus, _0x, valueCharacters, optionalL,]) => {
|
|
209
|
+
const value = valueCharacters.join('');
|
|
210
|
+
// If the 'L' suffix is present, use BigInt for long values
|
|
211
|
+
if (optionalL) {
|
|
212
|
+
const sign = optionalMinus ? -1n : 1n;
|
|
213
|
+
return sign * BigInt('0x' + value);
|
|
214
|
+
}
|
|
215
|
+
const sign = optionalMinus ? -1 : 1;
|
|
216
|
+
return sign * Number.parseInt(value, 16);
|
|
217
|
+
});
|
|
218
|
+
const smaliNumberParser = createUnionParser([
|
|
219
|
+
promiseCompose(createTupleParser([
|
|
220
|
+
createNegativeLookaheadParser(createUnionParser([
|
|
221
|
+
createExactSequenceParser('0x'),
|
|
222
|
+
createExactSequenceParser('-0x'),
|
|
223
|
+
])),
|
|
224
|
+
jsonNumberParser,
|
|
225
|
+
createOptionalParser(createUnionParser([
|
|
226
|
+
createExactSequenceParser('f'),
|
|
227
|
+
createExactSequenceParser('F'),
|
|
228
|
+
])),
|
|
229
|
+
]), ([_not0x, number, optionalFloatSuffix,]) => {
|
|
230
|
+
// If there's an 'f' or 'F' suffix, convert to 32-bit float precision
|
|
231
|
+
// to match what would be stored in a DEX file
|
|
232
|
+
if (optionalFloatSuffix) {
|
|
233
|
+
const float32Array = new Float32Array(1);
|
|
234
|
+
float32Array[0] = number;
|
|
235
|
+
return float32Array[0];
|
|
236
|
+
}
|
|
237
|
+
return number;
|
|
238
|
+
}),
|
|
239
|
+
promiseCompose(smaliHexNumberParser, value => {
|
|
240
|
+
// For smaliNumberParser, we need to ensure we return a number
|
|
241
|
+
// BigInt values from hex parser should be converted if they fit in number range
|
|
242
|
+
if (typeof value === 'bigint') {
|
|
243
|
+
// This shouldn't happen in contexts where smaliNumberParser is used
|
|
244
|
+
// (registers, line numbers, etc) but if it does, convert to number
|
|
245
|
+
return Number(value);
|
|
246
|
+
}
|
|
247
|
+
return value;
|
|
248
|
+
}),
|
|
249
|
+
]);
|
|
250
|
+
setParserName(smaliNumberParser, 'smaliNumberParser');
|
|
251
|
+
// Parser for field initial values that can include BigInt
|
|
252
|
+
const smaliFieldValueParser = createUnionParser([
|
|
253
|
+
promiseCompose(createTupleParser([
|
|
254
|
+
createNegativeLookaheadParser(createUnionParser([
|
|
255
|
+
createExactSequenceParser('0x'),
|
|
256
|
+
createExactSequenceParser('-0x'),
|
|
257
|
+
])),
|
|
258
|
+
jsonNumberParser,
|
|
259
|
+
createOptionalParser(createUnionParser([
|
|
260
|
+
createExactSequenceParser('f'),
|
|
261
|
+
createExactSequenceParser('F'),
|
|
262
|
+
])),
|
|
263
|
+
]), ([_not0x, number, optionalFloatSuffix,]) => {
|
|
264
|
+
// If there's an 'f' or 'F' suffix, convert to 32-bit float precision
|
|
265
|
+
// to match what would be stored in a DEX file
|
|
266
|
+
if (optionalFloatSuffix) {
|
|
267
|
+
const float32Array = new Float32Array(1);
|
|
268
|
+
float32Array[0] = number;
|
|
269
|
+
return float32Array[0];
|
|
270
|
+
}
|
|
271
|
+
return number;
|
|
272
|
+
}),
|
|
273
|
+
smaliHexNumberParser,
|
|
274
|
+
]);
|
|
275
|
+
const smaliQuotedStringParser = promiseCompose(jsonStringParser, string => string.replaceAll(String.raw `\'`, '\''));
|
|
276
|
+
// Parser for smali character literals (e.g., 'a', ':', '\'', '\\')
|
|
277
|
+
const smaliCharacterLiteralParser = promiseCompose(createTupleParser([
|
|
278
|
+
createExactSequenceParser('\''),
|
|
279
|
+
createDisjunctionParser([
|
|
280
|
+
// Handle escape sequences (must come before regular characters)
|
|
281
|
+
promiseCompose(createExactSequenceParser(String.raw `\\`), () => '\\'),
|
|
282
|
+
promiseCompose(createExactSequenceParser(String.raw `\'`), () => '\''),
|
|
283
|
+
promiseCompose(createExactSequenceParser(String.raw `\"`), () => '"'),
|
|
284
|
+
promiseCompose(createExactSequenceParser(String.raw `\n`), () => '\n'),
|
|
285
|
+
promiseCompose(createExactSequenceParser(String.raw `\r`), () => '\r'),
|
|
286
|
+
promiseCompose(createExactSequenceParser(String.raw `\t`), () => '\t'),
|
|
287
|
+
promiseCompose(createExactSequenceParser(String.raw `\b`), () => '\b'),
|
|
288
|
+
promiseCompose(createExactSequenceParser(String.raw `\f`), () => '\f'),
|
|
289
|
+
// Handle regular characters (not a single quote)
|
|
290
|
+
parserCreatorCompose(() => createElementParser(), character => async (parserContext) => {
|
|
291
|
+
parserContext.invariant(character !== '\'', 'Unexpected single quote');
|
|
292
|
+
return character;
|
|
293
|
+
})(),
|
|
294
|
+
]),
|
|
295
|
+
createExactSequenceParser('\''),
|
|
296
|
+
]), ([, character]) => character.charCodeAt(0));
|
|
297
|
+
setParserName(smaliCharacterLiteralParser, 'smaliCharacterLiteralParser');
|
|
298
|
+
// Parser that matches identifier continuation characters (letters, digits, $, -, _)
|
|
299
|
+
const smaliIdentifierContinuationParser = async (parserContext) => {
|
|
300
|
+
const character = await parserContext.peek(0);
|
|
301
|
+
parserContext.invariant(character !== undefined, 'Unexpected end of input');
|
|
302
|
+
invariant(character !== undefined, 'Unexpected end of input');
|
|
303
|
+
parserContext.invariant((character >= 'a' && character <= 'z')
|
|
304
|
+
|| (character >= 'A' && character <= 'Z')
|
|
305
|
+
|| (character >= '0' && character <= '9')
|
|
306
|
+
|| character === '$'
|
|
307
|
+
|| character === '-'
|
|
308
|
+
|| character === '_', 'Expected identifier continuation character, got "%s"', character);
|
|
309
|
+
parserContext.skip(1);
|
|
310
|
+
return character;
|
|
311
|
+
};
|
|
312
|
+
setParserName(smaliIdentifierContinuationParser, 'smaliIdentifierContinuationParser');
|
|
313
|
+
// Helper to create an access flag parser with word boundary check
|
|
314
|
+
const createAccessFlagParser = (keyword) => promiseCompose(createTupleParser([
|
|
315
|
+
createExactSequenceParser(keyword),
|
|
316
|
+
createNegativeLookaheadParser(smaliIdentifierContinuationParser),
|
|
317
|
+
]), ([flag]) => flag);
|
|
62
318
|
const smaliAccessFlagsParser = promiseCompose(createSeparatedArrayParser(createUnionParser([
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
transient: false,
|
|
85
|
-
varargs: false,
|
|
86
|
-
native: false,
|
|
87
|
-
interface: false,
|
|
88
|
-
abstract: false,
|
|
89
|
-
strict: false,
|
|
90
|
-
synthetic: false,
|
|
91
|
-
annotation: false,
|
|
92
|
-
enum: false,
|
|
93
|
-
constructor: false,
|
|
94
|
-
declaredSynchronized: false,
|
|
95
|
-
};
|
|
319
|
+
createAccessFlagParser('public'),
|
|
320
|
+
createAccessFlagParser('protected'),
|
|
321
|
+
createAccessFlagParser('private'),
|
|
322
|
+
createAccessFlagParser('final'),
|
|
323
|
+
createAccessFlagParser('bridge'),
|
|
324
|
+
createAccessFlagParser('synthetic'),
|
|
325
|
+
createAccessFlagParser('varargs'),
|
|
326
|
+
createAccessFlagParser('static'),
|
|
327
|
+
createAccessFlagParser('constructor'),
|
|
328
|
+
createAccessFlagParser('abstract'),
|
|
329
|
+
createAccessFlagParser('native'),
|
|
330
|
+
createAccessFlagParser('volatile'),
|
|
331
|
+
createAccessFlagParser('transient'),
|
|
332
|
+
createAccessFlagParser('synchronized'),
|
|
333
|
+
createAccessFlagParser('declared-synchronized'),
|
|
334
|
+
createAccessFlagParser('strict'),
|
|
335
|
+
createAccessFlagParser('interface'),
|
|
336
|
+
createAccessFlagParser('annotation'),
|
|
337
|
+
createAccessFlagParser('enum'),
|
|
338
|
+
]), smaliSingleWhitespaceParser), accessFlagNames => {
|
|
339
|
+
const accessFlags = dalvikExecutableAccessFlagsDefault();
|
|
96
340
|
for (const accessFlagName of accessFlagNames) {
|
|
97
|
-
|
|
341
|
+
if (accessFlagName === 'declared-synchronized') {
|
|
342
|
+
accessFlags.declaredSynchronized = true;
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
accessFlags[accessFlagName] = true;
|
|
346
|
+
}
|
|
98
347
|
}
|
|
99
348
|
return accessFlags;
|
|
100
349
|
});
|
|
101
350
|
const smaliClassDeclarationParser = promiseCompose(createTupleParser([
|
|
102
351
|
smaliCommentsOrNewlinesParser,
|
|
103
352
|
createExactSequenceParser('.class '),
|
|
104
|
-
|
|
105
|
-
|
|
353
|
+
createOptionalParser(promiseCompose(createTupleParser([
|
|
354
|
+
smaliAccessFlagsParser,
|
|
355
|
+
smaliSingleWhitespaceParser,
|
|
356
|
+
]), ([accessFlags,]) => accessFlags)),
|
|
106
357
|
smaliTypeDescriptorParser,
|
|
107
|
-
|
|
108
|
-
]), ([_commentsOrNewlines, _dotClass, accessFlags
|
|
358
|
+
smaliLineEndPraser,
|
|
359
|
+
]), ([_commentsOrNewlines, _dotClass, accessFlags = dalvikExecutableAccessFlagsDefault(), classPath,]) => ({
|
|
109
360
|
accessFlags,
|
|
110
361
|
class: classPath,
|
|
111
362
|
}));
|
|
@@ -113,7 +364,7 @@ setParserName(smaliClassDeclarationParser, 'smaliClassDeclarationParser');
|
|
|
113
364
|
const smaliSuperDeclarationParser = promiseCompose(createTupleParser([
|
|
114
365
|
createExactSequenceParser('.super '),
|
|
115
366
|
smaliTypeDescriptorParser,
|
|
116
|
-
|
|
367
|
+
smaliLineEndPraser,
|
|
117
368
|
]), ([_super, superclass, _newline,]) => ({
|
|
118
369
|
superclass,
|
|
119
370
|
}));
|
|
@@ -121,39 +372,192 @@ setParserName(smaliSuperDeclarationParser, 'smaliSuperDeclarationParser');
|
|
|
121
372
|
const smaliInterfaceDeclarationParser = promiseCompose(createTupleParser([
|
|
122
373
|
createExactSequenceParser('.implements '),
|
|
123
374
|
smaliTypeDescriptorParser,
|
|
124
|
-
|
|
375
|
+
smaliLineEndPraser,
|
|
125
376
|
]), ([_interface, interface_, _newline,]) => interface_);
|
|
126
377
|
setParserName(smaliInterfaceDeclarationParser, 'smaliInterfaceDeclarationParser');
|
|
127
378
|
const smaliSourceDeclarationParser = promiseCompose(createTupleParser([
|
|
128
379
|
createExactSequenceParser('.source '),
|
|
129
380
|
smaliQuotedStringParser,
|
|
130
|
-
|
|
381
|
+
smaliLineEndPraser,
|
|
131
382
|
]), ([_source, sourceFile, _newline,]) => ({
|
|
132
383
|
sourceFile,
|
|
133
384
|
}));
|
|
385
|
+
const smaliEnumValueParser = promiseCompose(createTupleParser([
|
|
386
|
+
createExactSequenceParser('.enum '),
|
|
387
|
+
smaliTypeDescriptorParser,
|
|
388
|
+
createExactSequenceParser('->'),
|
|
389
|
+
smaliMemberNameParser,
|
|
390
|
+
createExactSequenceParser(':'),
|
|
391
|
+
smaliTypeDescriptorParser,
|
|
392
|
+
]), ([_enum, classType, _arrow, fieldName, _colon, fieldType,]) => ({
|
|
393
|
+
class: classType,
|
|
394
|
+
type: fieldType,
|
|
395
|
+
name: fieldName,
|
|
396
|
+
}));
|
|
397
|
+
setParserName(smaliEnumValueParser, 'smaliEnumValueParser');
|
|
398
|
+
const smaliMethodPrototypeParser = promiseCompose(createTupleParser([
|
|
399
|
+
createExactSequenceParser('('),
|
|
400
|
+
createArrayParser(smaliTypeDescriptorParser),
|
|
401
|
+
createExactSequenceParser(')'),
|
|
402
|
+
smaliTypeDescriptorParser,
|
|
403
|
+
]), ([_openParenthesis, parameters, _closeParenthesis, returnType,]) => ({
|
|
404
|
+
parameters,
|
|
405
|
+
returnType,
|
|
406
|
+
shorty: shortyFromLongy(returnType) + parameters.map(parameter => {
|
|
407
|
+
if (parameter === 'V') {
|
|
408
|
+
return '';
|
|
409
|
+
}
|
|
410
|
+
return shortyFromLongy(parameter);
|
|
411
|
+
}).join(''),
|
|
412
|
+
}));
|
|
413
|
+
setParserName(smaliMethodPrototypeParser, 'smaliMethodPrototypeParser');
|
|
414
|
+
const smaliParametersMethodParser = promiseCompose(createTupleParser([
|
|
415
|
+
smaliTypeDescriptorParser,
|
|
416
|
+
createExactSequenceParser('->'),
|
|
417
|
+
smaliMemberNameParser,
|
|
418
|
+
smaliMethodPrototypeParser,
|
|
419
|
+
]), ([classPath, _separator, methodName, prototype,]) => ({
|
|
420
|
+
class: classPath,
|
|
421
|
+
prototype,
|
|
422
|
+
name: methodName,
|
|
423
|
+
}));
|
|
424
|
+
setParserName(smaliParametersMethodParser, 'smaliParametersMethodParser');
|
|
425
|
+
// Forward declaration to handle circular reference
|
|
426
|
+
let smaliSubannotationParser;
|
|
427
|
+
const smaliAnnotationElementParser = promiseCompose(createTupleParser([
|
|
428
|
+
smaliIndentationParser,
|
|
429
|
+
smaliIdentifierParser,
|
|
430
|
+
createExactSequenceParser(' = '),
|
|
431
|
+
createDisjunctionParser([
|
|
432
|
+
promiseCompose(createParserAccessorParser(() => smaliSubannotationParser), value => ({ kind: 'raw', value })),
|
|
433
|
+
promiseCompose(smaliEnumValueParser, value => ({ kind: 'enum', value })),
|
|
434
|
+
promiseCompose(smaliQuotedStringParser, value => ({ kind: 'string', value })),
|
|
435
|
+
promiseCompose(smaliParametersMethodParser, value => ({ kind: 'raw', value })),
|
|
436
|
+
promiseCompose(smaliTypeDescriptorParser, value => ({ kind: 'type', value })),
|
|
437
|
+
promiseCompose(smaliNumberParser, value => ({ kind: 'raw', value })),
|
|
438
|
+
promiseCompose(createExactSequenceParser('true'), () => ({ kind: 'raw', value: true })),
|
|
439
|
+
promiseCompose(createExactSequenceParser('false'), () => ({ kind: 'raw', value: false })),
|
|
440
|
+
promiseCompose(createExactSequenceParser('null'), () => ({ kind: 'raw', value: null })),
|
|
441
|
+
promiseCompose(createExactSequenceParser('{}'), () => ({ kind: 'raw', value: [] })),
|
|
442
|
+
promiseCompose(createTupleParser([
|
|
443
|
+
createExactSequenceParser('{\n'),
|
|
444
|
+
createSeparatedArrayParser(promiseCompose(createTupleParser([
|
|
445
|
+
smaliIndentationParser,
|
|
446
|
+
smaliEnumValueParser,
|
|
447
|
+
]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
|
|
448
|
+
smaliLineEndPraser,
|
|
449
|
+
smaliIndentationParser,
|
|
450
|
+
createExactSequenceParser('}'),
|
|
451
|
+
]), ([_openBrace, value, _closeBrace,]) => ({ kind: 'enum', value })),
|
|
452
|
+
promiseCompose(createTupleParser([
|
|
453
|
+
createExactSequenceParser('{\n'),
|
|
454
|
+
createSeparatedArrayParser(promiseCompose(createTupleParser([
|
|
455
|
+
smaliIndentationParser,
|
|
456
|
+
smaliTypeDescriptorParser,
|
|
457
|
+
]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
|
|
458
|
+
smaliLineEndPraser,
|
|
459
|
+
smaliIndentationParser,
|
|
460
|
+
createExactSequenceParser('}'),
|
|
461
|
+
]), ([_openBrace, value, _closeBrace,]) => ({ kind: 'type', value })),
|
|
462
|
+
promiseCompose(createTupleParser([
|
|
463
|
+
createExactSequenceParser('{\n'),
|
|
464
|
+
createSeparatedArrayParser(promiseCompose(createTupleParser([
|
|
465
|
+
smaliIndentationParser,
|
|
466
|
+
smaliQuotedStringParser,
|
|
467
|
+
]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
|
|
468
|
+
smaliLineEndPraser,
|
|
469
|
+
smaliIndentationParser,
|
|
470
|
+
createExactSequenceParser('}'),
|
|
471
|
+
]), ([_openBrace, value, _closeBrace,]) => ({ kind: 'string', value })),
|
|
472
|
+
promiseCompose(createTupleParser([
|
|
473
|
+
createExactSequenceParser('{\n'),
|
|
474
|
+
createSeparatedArrayParser(promiseCompose(createTupleParser([
|
|
475
|
+
smaliIndentationParser,
|
|
476
|
+
smaliNumberParser,
|
|
477
|
+
]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
|
|
478
|
+
smaliLineEndPraser,
|
|
479
|
+
smaliIndentationParser,
|
|
480
|
+
createExactSequenceParser('}'),
|
|
481
|
+
]), ([_openBrace, value, _closeBrace,]) => ({ kind: 'raw', value })),
|
|
482
|
+
]),
|
|
483
|
+
smaliLineEndPraser,
|
|
484
|
+
]), ([_indentation, name, _equalsSign, value, _newline,]) => ({
|
|
485
|
+
name,
|
|
486
|
+
value: value,
|
|
487
|
+
}));
|
|
488
|
+
setParserName(smaliAnnotationElementParser, 'smaliAnnotationElementParser');
|
|
489
|
+
// Now define the subannotation parser
|
|
490
|
+
smaliSubannotationParser = promiseCompose(createTupleParser([
|
|
491
|
+
createExactSequenceParser('.subannotation '),
|
|
492
|
+
smaliTypeDescriptorParser,
|
|
493
|
+
smaliLineEndPraser,
|
|
494
|
+
createArrayParser(smaliAnnotationElementParser),
|
|
495
|
+
smaliIndentationParser,
|
|
496
|
+
createExactSequenceParser('.end subannotation'),
|
|
497
|
+
]), ([_subannotation, type, _newline, elements, _indentation, _endSubannotation,]) => ({
|
|
498
|
+
type,
|
|
499
|
+
elements,
|
|
500
|
+
}));
|
|
501
|
+
setParserName(smaliSubannotationParser, 'smaliSubannotationParser');
|
|
502
|
+
export const smaliAnnotationParser = promiseCompose(createTupleParser([
|
|
503
|
+
smaliIndentationParser,
|
|
504
|
+
createExactSequenceParser('.annotation '),
|
|
505
|
+
createUnionParser([
|
|
506
|
+
createExactSequenceParser('build'),
|
|
507
|
+
createExactSequenceParser('runtime'),
|
|
508
|
+
createExactSequenceParser('system'),
|
|
509
|
+
]),
|
|
510
|
+
smaliSingleWhitespaceParser,
|
|
511
|
+
smaliTypeDescriptorParser,
|
|
512
|
+
smaliLineEndPraser,
|
|
513
|
+
createArrayParser(smaliAnnotationElementParser),
|
|
514
|
+
smaliIndentationParser,
|
|
515
|
+
createExactSequenceParser('.end annotation\n'),
|
|
516
|
+
]), ([_indentation0, _annotation, visibility, _space, type, _newline, elements, _indentation1, _endAnnotation,]) => ({
|
|
517
|
+
type,
|
|
518
|
+
elements,
|
|
519
|
+
visibility,
|
|
520
|
+
}));
|
|
521
|
+
setParserName(smaliAnnotationParser, 'smaliAnnotationParser');
|
|
134
522
|
export const smaliFieldParser = promiseCompose(createTupleParser([
|
|
135
523
|
createExactSequenceParser('.field '),
|
|
136
|
-
|
|
137
|
-
|
|
524
|
+
createOptionalParser(promiseCompose(createTupleParser([
|
|
525
|
+
smaliAccessFlagsParser,
|
|
526
|
+
smaliSingleWhitespaceParser,
|
|
527
|
+
]), ([accessFlags, _space]) => accessFlags)),
|
|
138
528
|
smaliMemberNameParser,
|
|
139
529
|
createExactSequenceParser(':'),
|
|
140
530
|
smaliTypeDescriptorParser,
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
531
|
+
createOptionalParser(promiseCompose(createTupleParser([
|
|
532
|
+
createExactSequenceParser(' = '),
|
|
533
|
+
createUnionParser([
|
|
534
|
+
smaliCharacterLiteralParser,
|
|
535
|
+
smaliFieldValueParser,
|
|
536
|
+
smaliQuotedStringParser,
|
|
537
|
+
promiseCompose(createExactSequenceParser('true'), () => true),
|
|
538
|
+
promiseCompose(createExactSequenceParser('false'), () => false),
|
|
539
|
+
promiseCompose(createExactSequenceParser('null'), () => null),
|
|
540
|
+
]),
|
|
541
|
+
]), ([_equals, value,]) => value)),
|
|
542
|
+
smaliLineEndPraser,
|
|
543
|
+
createOptionalParser(promiseCompose(createTupleParser([
|
|
544
|
+
createSeparatedArrayParser(smaliAnnotationParser, smaliNewlinesParser),
|
|
545
|
+
createExactSequenceParser('.end field\n'),
|
|
546
|
+
]), ([annotations, _endField,]) => annotations)),
|
|
547
|
+
]), ([_field, accessFlags, name, _colon, type, initialValue, _newline, annotations,]) => ({
|
|
548
|
+
field: {
|
|
549
|
+
accessFlags: accessFlags ?? dalvikExecutableAccessFlagsDefault(),
|
|
145
550
|
field: {
|
|
146
551
|
class: 'FILLED_LATER',
|
|
147
552
|
type,
|
|
148
553
|
name,
|
|
149
554
|
},
|
|
150
|
-
}
|
|
151
|
-
|
|
555
|
+
},
|
|
556
|
+
annotations: annotations ?? [],
|
|
557
|
+
...(initialValue !== undefined ? { initialValue } : {}),
|
|
558
|
+
}));
|
|
152
559
|
setParserName(smaliFieldParser, 'smaliFieldParser');
|
|
153
|
-
const smaliFieldsParser = promiseCompose(
|
|
154
|
-
smaliFieldParser,
|
|
155
|
-
smaliCommentsOrNewlinesParser,
|
|
156
|
-
])), (fieldsAndComments) => {
|
|
560
|
+
const smaliFieldsParser = promiseCompose(createSeparatedNonEmptyArrayParser(smaliFieldParser, smaliCommentsOrNewlinesParser), fieldsAndComments => {
|
|
157
561
|
let type = 'instanceField';
|
|
158
562
|
const staticFields = [];
|
|
159
563
|
const instanceFields = [];
|
|
@@ -171,7 +575,8 @@ const smaliFieldsParser = promiseCompose(createArrayParser(createDisjunctionPars
|
|
|
171
575
|
}
|
|
172
576
|
invariant(typeof fieldOrComment === 'object', 'Expected field or comment');
|
|
173
577
|
const field = fieldOrComment;
|
|
174
|
-
if (type === 'staticField'
|
|
578
|
+
if (type === 'staticField'
|
|
579
|
+
|| field.field.accessFlags.static) {
|
|
175
580
|
staticFields.push(field);
|
|
176
581
|
continue;
|
|
177
582
|
}
|
|
@@ -203,85 +608,28 @@ const smaliShortyReturnTypeParser = createUnionParser([
|
|
|
203
608
|
smaliShortyFieldTypeParser,
|
|
204
609
|
]);
|
|
205
610
|
setParserName(smaliShortyReturnTypeParser, 'smaliShortyReturnTypeParser');
|
|
206
|
-
function
|
|
207
|
-
|
|
208
|
-
|
|
611
|
+
function shortyGetInsSize(shorty) {
|
|
612
|
+
let size = 0;
|
|
613
|
+
for (const char of shorty.slice(1)) {
|
|
614
|
+
if (char === 'J' || char === 'D') {
|
|
615
|
+
size += 2;
|
|
616
|
+
}
|
|
617
|
+
else if (char !== 'V') {
|
|
618
|
+
size += 1;
|
|
619
|
+
}
|
|
209
620
|
}
|
|
210
|
-
return
|
|
621
|
+
return size;
|
|
211
622
|
}
|
|
212
|
-
const smaliMethodPrototypeParser = promiseCompose(createTupleParser([
|
|
213
|
-
createExactSequenceParser('('),
|
|
214
|
-
createArrayParser(smaliTypeDescriptorParser),
|
|
215
|
-
createExactSequenceParser(')'),
|
|
216
|
-
smaliTypeDescriptorParser,
|
|
217
|
-
]), ([_openParenthesis, parameters, _closeParenthesis, returnType,]) => ({
|
|
218
|
-
parameters,
|
|
219
|
-
returnType,
|
|
220
|
-
shorty: shortyFromLongy(returnType) + parameters.map((parameter) => {
|
|
221
|
-
if (parameter === 'V') {
|
|
222
|
-
return '';
|
|
223
|
-
}
|
|
224
|
-
return shortyFromLongy(parameter);
|
|
225
|
-
}).join(''),
|
|
226
|
-
}));
|
|
227
|
-
setParserName(smaliMethodPrototypeParser, 'smaliMethodPrototypeParser');
|
|
228
623
|
const smaliCodeRegistersParser = promiseCompose(createTupleParser([
|
|
229
624
|
createExactSequenceParser(' .registers '),
|
|
230
|
-
|
|
231
|
-
|
|
625
|
+
smaliNumberParser,
|
|
626
|
+
smaliLineEndPraser,
|
|
232
627
|
]), ([_registers, registers, _newline,]) => registers);
|
|
233
628
|
setParserName(smaliCodeRegistersParser, 'smaliCodeRegistersParser');
|
|
234
|
-
export const smaliAnnotationParser = promiseCompose(createTupleParser([
|
|
235
|
-
smaliOptionalIndentationParser,
|
|
236
|
-
createExactSequenceParser('.annotation '),
|
|
237
|
-
createUnionParser([
|
|
238
|
-
createExactSequenceParser('build'),
|
|
239
|
-
createExactSequenceParser('runtime'),
|
|
240
|
-
createExactSequenceParser('system'),
|
|
241
|
-
]),
|
|
242
|
-
createExactSequenceParser(' '),
|
|
243
|
-
smaliTypeDescriptorParser,
|
|
244
|
-
createExactSequenceParser('\n'),
|
|
245
|
-
smaliOptionalIndentationParser,
|
|
246
|
-
createOptionalParser(promiseCompose(createTupleParser([
|
|
247
|
-
createExactSequenceParser('value = '),
|
|
248
|
-
createUnionParser([
|
|
249
|
-
smaliTypeDescriptorParser,
|
|
250
|
-
promiseCompose(createTupleParser([
|
|
251
|
-
createExactSequenceParser('{\n'),
|
|
252
|
-
createSeparatedArrayParser(promiseCompose(createTupleParser([
|
|
253
|
-
smaliOptionalIndentationParser,
|
|
254
|
-
smaliTypeDescriptorParser,
|
|
255
|
-
]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
|
|
256
|
-
createExactSequenceParser('\n'),
|
|
257
|
-
smaliOptionalIndentationParser,
|
|
258
|
-
createExactSequenceParser('}'),
|
|
259
|
-
]), ([_openBrace, value, _closeBrace,]) => value),
|
|
260
|
-
promiseCompose(createTupleParser([
|
|
261
|
-
createExactSequenceParser('{\n'),
|
|
262
|
-
createSeparatedArrayParser(promiseCompose(createTupleParser([
|
|
263
|
-
smaliOptionalIndentationParser,
|
|
264
|
-
smaliQuotedStringParser,
|
|
265
|
-
]), ([_indentation, value,]) => value), createExactSequenceParser(',\n')),
|
|
266
|
-
createExactSequenceParser('\n'),
|
|
267
|
-
smaliOptionalIndentationParser,
|
|
268
|
-
createExactSequenceParser('}'),
|
|
269
|
-
]), ([_openBrace, value, _closeBrace,]) => value),
|
|
270
|
-
]),
|
|
271
|
-
createExactSequenceParser('\n'),
|
|
272
|
-
]), ([_indentation, value, _newline,]) => value)),
|
|
273
|
-
smaliOptionalIndentationParser,
|
|
274
|
-
createExactSequenceParser('.end annotation\n'),
|
|
275
|
-
]), ([_indentation0, _annotation, _accessFlags, _space, type, _newline, _indentation1, value, _endAnnotation,]) => ({
|
|
276
|
-
type,
|
|
277
|
-
value,
|
|
278
|
-
// accessFlags,
|
|
279
|
-
}));
|
|
280
|
-
setParserName(smaliAnnotationParser, 'smaliAnnotationParser');
|
|
281
629
|
const smaliCodeLineParser = promiseCompose(createTupleParser([
|
|
282
630
|
createExactSequenceParser(' .line '),
|
|
283
|
-
|
|
284
|
-
|
|
631
|
+
smaliNumberParser,
|
|
632
|
+
smaliLineEndPraser,
|
|
285
633
|
]), ([_line, line, _newline,]) => line);
|
|
286
634
|
setParserName(smaliCodeLineParser, 'smaliCodeLineParser');
|
|
287
635
|
function isSmaliRegister(value) {
|
|
@@ -295,11 +643,11 @@ function isSmaliRegister(value) {
|
|
|
295
643
|
const smaliParametersRegisterParser = promiseCompose(createUnionParser([
|
|
296
644
|
createTupleParser([
|
|
297
645
|
createExactSequenceParser('v'),
|
|
298
|
-
|
|
646
|
+
smaliNumberParser,
|
|
299
647
|
]),
|
|
300
648
|
createTupleParser([
|
|
301
649
|
createExactSequenceParser('p'),
|
|
302
|
-
|
|
650
|
+
smaliNumberParser,
|
|
303
651
|
]),
|
|
304
652
|
]), ([prefix, index,]) => ({
|
|
305
653
|
prefix,
|
|
@@ -311,34 +659,133 @@ const smaliCodeLocalParser = promiseCompose(createTupleParser([
|
|
|
311
659
|
smaliParametersRegisterParser,
|
|
312
660
|
createOptionalParser(createTupleParser([
|
|
313
661
|
createExactSequenceParser(','),
|
|
314
|
-
|
|
315
|
-
|
|
662
|
+
smaliWhitespaceParser,
|
|
663
|
+
smaliQuotedStringParser,
|
|
316
664
|
createExactSequenceParser(':'),
|
|
317
665
|
smaliTypeDescriptorParser,
|
|
318
666
|
])),
|
|
319
|
-
|
|
320
|
-
]), ([_local,
|
|
667
|
+
smaliLineEndPraser,
|
|
668
|
+
]), ([_local, register, nameAndType,]) => ({
|
|
669
|
+
register,
|
|
670
|
+
name: nameAndType?.[2],
|
|
671
|
+
type: nameAndType?.[4],
|
|
672
|
+
}));
|
|
321
673
|
setParserName(smaliCodeLocalParser, 'smaliCodeLocalParser');
|
|
674
|
+
const smaliCodeEndLocalParser = promiseCompose(createTupleParser([
|
|
675
|
+
createExactSequenceParser(' .end local '),
|
|
676
|
+
smaliParametersRegisterParser,
|
|
677
|
+
smaliLineEndPraser,
|
|
678
|
+
]), ([_endLocal, register, _newline,]) => register);
|
|
679
|
+
setParserName(smaliCodeEndLocalParser, 'smaliCodeEndLocalParser');
|
|
680
|
+
const smaliCodeRestartLocalParser = promiseCompose(createTupleParser([
|
|
681
|
+
createExactSequenceParser(' .restart local '),
|
|
682
|
+
smaliParametersRegisterParser,
|
|
683
|
+
smaliLineEndPraser,
|
|
684
|
+
]), ([_restartLocal, register, _newline,]) => register);
|
|
685
|
+
setParserName(smaliCodeRestartLocalParser, 'smaliCodeRestartLocalParser');
|
|
686
|
+
const smaliCodePrologueEndParser = promiseCompose(createTupleParser([
|
|
687
|
+
createExactSequenceParser(' .prologue'),
|
|
688
|
+
smaliLineEndPraser,
|
|
689
|
+
]), () => true);
|
|
690
|
+
setParserName(smaliCodePrologueEndParser, 'smaliCodePrologueEndParser');
|
|
691
|
+
const smaliCodeEpilogueBeginParser = promiseCompose(createTupleParser([
|
|
692
|
+
createExactSequenceParser(' .epilogue'),
|
|
693
|
+
smaliLineEndPraser,
|
|
694
|
+
]), () => true);
|
|
695
|
+
setParserName(smaliCodeEpilogueBeginParser, 'smaliCodeEpilogueBeginParser');
|
|
322
696
|
export const smaliCodeParameterParser = promiseCompose(createTupleParser([
|
|
323
697
|
createExactSequenceParser(' .param '),
|
|
324
698
|
smaliParametersRegisterParser,
|
|
325
699
|
createOptionalParser(createTupleParser([
|
|
326
700
|
createExactSequenceParser(','),
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
701
|
+
smaliWhitespaceParser,
|
|
702
|
+
smaliQuotedStringParser,
|
|
703
|
+
smaliWhitespaceParser,
|
|
330
704
|
])),
|
|
331
|
-
createOptionalParser(
|
|
332
|
-
|
|
333
|
-
|
|
705
|
+
createOptionalParser(smaliWhitespaceParser),
|
|
706
|
+
smaliCommentsOrNewlinesParser,
|
|
707
|
+
createOptionalParser(createTupleParser([
|
|
708
|
+
smaliAnnotationParser,
|
|
709
|
+
smaliIndentationParser,
|
|
710
|
+
createExactSequenceParser('.end param\n'),
|
|
711
|
+
])),
|
|
712
|
+
]), ([_parameter, register, optionalCommaAndString, _whitespace, _commentOrNewline, optionalAnnotation,]) => {
|
|
713
|
+
let annotation;
|
|
714
|
+
if (optionalAnnotation) {
|
|
715
|
+
annotation = optionalAnnotation[0];
|
|
716
|
+
}
|
|
717
|
+
return {
|
|
718
|
+
register,
|
|
719
|
+
name: optionalCommaAndString?.[2],
|
|
720
|
+
annotation,
|
|
721
|
+
};
|
|
722
|
+
});
|
|
334
723
|
setParserName(smaliCodeParameterParser, 'smaliCodeParameterParser');
|
|
335
724
|
const smaliCodeLabelParser = promiseCompose(createTupleParser([
|
|
336
|
-
createExactSequenceParser('
|
|
725
|
+
createExactSequenceParser(':'),
|
|
337
726
|
smaliIdentifierParser,
|
|
338
|
-
|
|
339
|
-
]), ([_label, label, _newlabel,]) => label);
|
|
727
|
+
]), ([_label, label,]) => label);
|
|
340
728
|
setParserName(smaliCodeLabelParser, 'smaliCodeLabelParser');
|
|
341
|
-
const
|
|
729
|
+
const smaliCodeLabelLineParser = promiseCompose(createTupleParser([
|
|
730
|
+
smaliIndentationParser,
|
|
731
|
+
smaliCodeLabelParser,
|
|
732
|
+
smaliLineEndPraser,
|
|
733
|
+
]), ([_label, label, _newlabel,]) => label);
|
|
734
|
+
setParserName(smaliCodeLabelLineParser, 'smaliCodeLabelLineParser');
|
|
735
|
+
const smaliCatchDirectiveParser = promiseCompose(createTupleParser([
|
|
736
|
+
smaliIndentationParser,
|
|
737
|
+
createExactSequenceParser('.catch'),
|
|
738
|
+
createUnionParser([
|
|
739
|
+
promiseCompose(createExactSequenceParser('all'), () => undefined),
|
|
740
|
+
promiseCompose(createTupleParser([
|
|
741
|
+
createExactSequenceParser(' '),
|
|
742
|
+
smaliTypeDescriptorParser,
|
|
743
|
+
]), ([_space, type,]) => type),
|
|
744
|
+
]),
|
|
745
|
+
createExactSequenceParser(' {'),
|
|
746
|
+
smaliCodeLabelParser,
|
|
747
|
+
createExactSequenceParser(' .. '),
|
|
748
|
+
smaliCodeLabelParser,
|
|
749
|
+
createExactSequenceParser('} '),
|
|
750
|
+
smaliCodeLabelParser,
|
|
751
|
+
smaliLineEndPraser,
|
|
752
|
+
]), ([_indentation, _catch, type, _openBrace, startLabel, _dots, endLabel, _closeBrace, handlerLabel, _newline,]) => ({
|
|
753
|
+
type,
|
|
754
|
+
startLabel,
|
|
755
|
+
endLabel,
|
|
756
|
+
handlerLabel,
|
|
757
|
+
}));
|
|
758
|
+
setParserName(smaliCatchDirectiveParser, 'smaliCatchDirectiveParser');
|
|
759
|
+
const smaliLabeledCatchDirectiveParser = promiseCompose(createTupleParser([
|
|
760
|
+
createArrayParser(smaliCodeLineParser),
|
|
761
|
+
createOptionalParser(smaliCodeLocalParser),
|
|
762
|
+
createArrayParser(smaliCodeLabelLineParser),
|
|
763
|
+
smaliCatchDirectiveParser,
|
|
764
|
+
]), ([_lines, _local, labels, catchDirective,]) => ({
|
|
765
|
+
labels,
|
|
766
|
+
catchDirective,
|
|
767
|
+
}));
|
|
768
|
+
setParserName(smaliLabeledCatchDirectiveParser, 'smaliLabeledCatchDirectiveParser');
|
|
769
|
+
const smaliParametersRegisterRangeParser = promiseCompose(createTupleParser([
|
|
770
|
+
createExactSequenceParser('{'),
|
|
771
|
+
smaliParametersRegisterParser,
|
|
772
|
+
createExactSequenceParser(' .. '),
|
|
773
|
+
smaliParametersRegisterParser,
|
|
774
|
+
createExactSequenceParser('}'),
|
|
775
|
+
]), ([_openBrace, startRegister, _dotDot, endRegister, _closeBrace,]) => {
|
|
776
|
+
invariant(startRegister.prefix === endRegister.prefix, 'Register range must use the same prefix');
|
|
777
|
+
invariant(startRegister.index <= endRegister.index, 'Register range start must be less than or equal to end');
|
|
778
|
+
const registers = [];
|
|
779
|
+
for (let i = startRegister.index; i <= endRegister.index; i++) {
|
|
780
|
+
registers.push({
|
|
781
|
+
prefix: startRegister.prefix,
|
|
782
|
+
index: i,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
return registers;
|
|
786
|
+
});
|
|
787
|
+
setParserName(smaliParametersRegisterRangeParser, 'smaliParametersRegisterRangeParser');
|
|
788
|
+
const smaliParametersRegisterListParser = promiseCompose(createTupleParser([
|
|
342
789
|
createExactSequenceParser('{'),
|
|
343
790
|
createArrayParser(promiseCompose(createTupleParser([
|
|
344
791
|
smaliParametersRegisterParser,
|
|
@@ -346,10 +793,16 @@ const smaliParametersRegistersParser = promiseCompose(createTupleParser([
|
|
|
346
793
|
]), ([parameter, _comma,]) => parameter)),
|
|
347
794
|
createExactSequenceParser('}'),
|
|
348
795
|
]), ([_openBrace, parameters, _closeBrace,]) => parameters);
|
|
796
|
+
setParserName(smaliParametersRegisterListParser, 'smaliParametersRegisterListParser');
|
|
797
|
+
const smaliParametersRegistersParser = createUnionParser([
|
|
798
|
+
smaliParametersRegisterRangeParser,
|
|
799
|
+
smaliParametersRegisterListParser,
|
|
800
|
+
]);
|
|
349
801
|
setParserName(smaliParametersRegistersParser, 'smaliParametersRegistersParser');
|
|
350
802
|
const smaliParametersStringParser = cloneParser(smaliQuotedStringParser);
|
|
351
803
|
setParserName(smaliParametersStringParser, 'smaliParametersStringParser');
|
|
352
804
|
const smaliParametersIntegerParser = promiseCompose(createTupleParser([
|
|
805
|
+
createOptionalParser(createExactSequenceParser('-')),
|
|
353
806
|
createExactSequenceParser('0x'),
|
|
354
807
|
async (parserContext) => {
|
|
355
808
|
const characters = [];
|
|
@@ -368,12 +821,18 @@ const smaliParametersIntegerParser = promiseCompose(createTupleParser([
|
|
|
368
821
|
}
|
|
369
822
|
return characters.join('');
|
|
370
823
|
},
|
|
371
|
-
createOptionalParser(
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
824
|
+
createOptionalParser(createUnionParser([
|
|
825
|
+
createExactSequenceParser('L'),
|
|
826
|
+
createExactSequenceParser('t'),
|
|
827
|
+
createExactSequenceParser('s'),
|
|
828
|
+
])),
|
|
829
|
+
]), ([optionalMinus, _0x, value, optionalSuffix,]) => {
|
|
830
|
+
if (optionalSuffix === 'L') {
|
|
831
|
+
const sign = optionalMinus ? -1n : 1n;
|
|
832
|
+
return sign * BigInt('0x' + value);
|
|
375
833
|
}
|
|
376
|
-
|
|
834
|
+
const sign = optionalMinus ? -1 : 1;
|
|
835
|
+
return sign * Number.parseInt(value, 16);
|
|
377
836
|
});
|
|
378
837
|
setParserName(smaliParametersIntegerParser, 'smaliParametersIntegerParser');
|
|
379
838
|
const smaliParametersLabelParser = promiseCompose(createTupleParser([
|
|
@@ -383,17 +842,6 @@ const smaliParametersLabelParser = promiseCompose(createTupleParser([
|
|
|
383
842
|
setParserName(smaliParametersLabelParser, 'smaliParametersLabelParser');
|
|
384
843
|
const smaliParametersTypeParser = cloneParser(smaliTypeDescriptorParser);
|
|
385
844
|
setParserName(smaliParametersTypeParser, 'smaliParametersTypeParser');
|
|
386
|
-
const smaliParametersMethodParser = promiseCompose(createTupleParser([
|
|
387
|
-
smaliTypeDescriptorParser,
|
|
388
|
-
createExactSequenceParser('->'),
|
|
389
|
-
smaliMemberNameParser,
|
|
390
|
-
smaliMethodPrototypeParser,
|
|
391
|
-
]), ([classPath, _separator, methodName, prototype,]) => ({
|
|
392
|
-
class: classPath,
|
|
393
|
-
prototype,
|
|
394
|
-
name: methodName,
|
|
395
|
-
}));
|
|
396
|
-
setParserName(smaliParametersMethodParser, 'smaliParametersMethodParser');
|
|
397
845
|
const smaliParametersFieldParser = promiseCompose(createTupleParser([
|
|
398
846
|
smaliTypeDescriptorParser,
|
|
399
847
|
createExactSequenceParser('->'),
|
|
@@ -412,6 +860,10 @@ const smaliCodeOperationParametersParser = createArrayParser(promiseCompose(crea
|
|
|
412
860
|
smaliParametersRegistersParser,
|
|
413
861
|
smaliParametersStringParser,
|
|
414
862
|
smaliParametersIntegerParser,
|
|
863
|
+
promiseCompose(createTupleParser([
|
|
864
|
+
createNegativeLookaheadParser(smaliParametersIntegerParser),
|
|
865
|
+
smaliNumberParser,
|
|
866
|
+
]), ([_notInteger, number]) => number),
|
|
415
867
|
smaliParametersLabelParser,
|
|
416
868
|
promiseCompose(createTupleParser([
|
|
417
869
|
createNegativeLookaheadParser(smaliParametersMethodParser),
|
|
@@ -425,44 +877,134 @@ const smaliCodeOperationParametersParser = createArrayParser(promiseCompose(crea
|
|
|
425
877
|
]), ([parameter, _comma,]) => parameter));
|
|
426
878
|
setParserName(smaliCodeOperationParametersParser, 'smaliCodeOperationParametersParser');
|
|
427
879
|
const smaliCodeOperationNameParser = promiseCompose(createTupleParser([
|
|
428
|
-
promiseCompose(createSeparatedArrayParser(smaliIdentifierParser, createExactSequenceParser('-')),
|
|
880
|
+
promiseCompose(createSeparatedArrayParser(smaliIdentifierParser, createExactSequenceParser('-')), parts => parts.join('-')),
|
|
429
881
|
createOptionalParser(promiseCompose(createTupleParser([
|
|
430
882
|
createExactSequenceParser('/'),
|
|
431
883
|
smaliIdentifierParser,
|
|
432
884
|
]), ([slash, name]) => slash + name)),
|
|
433
885
|
]), ([name, optionalSlashName,]) => name + (optionalSlashName || ''));
|
|
434
886
|
setParserName(smaliCodeOperationNameParser, 'smaliCodeOperationNameParser');
|
|
435
|
-
const
|
|
436
|
-
|
|
887
|
+
const smaliOneLineCodeOperationParser = promiseCompose(createTupleParser([
|
|
888
|
+
smaliSingleIndentationParser,
|
|
437
889
|
smaliCodeOperationNameParser,
|
|
438
890
|
promiseCompose(createOptionalParser(createTupleParser([
|
|
439
|
-
|
|
891
|
+
smaliSingleWhitespaceParser,
|
|
440
892
|
smaliCodeOperationParametersParser,
|
|
441
|
-
])),
|
|
442
|
-
|
|
893
|
+
])), undefinedOrParameters => undefinedOrParameters === undefined ? [] : undefinedOrParameters[1]),
|
|
894
|
+
smaliLineEndPraser,
|
|
443
895
|
]), ([_indent, operation, parameters, _newline,]) => ({
|
|
444
896
|
operation,
|
|
445
897
|
parameters,
|
|
446
898
|
}));
|
|
447
|
-
setParserName(
|
|
448
|
-
const
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
899
|
+
setParserName(smaliOneLineCodeOperationParser, 'smaliOneLineCodeOperationParser');
|
|
900
|
+
const createMultilineSmaliCodeOperationLabelsBodyParser = operationName => promiseCompose(createTupleParser([
|
|
901
|
+
createArrayParser(promiseCompose(createTupleParser([
|
|
902
|
+
smaliIndentationParser,
|
|
903
|
+
smaliCodeLabelParser,
|
|
904
|
+
smaliLineEndPraser,
|
|
905
|
+
]), ([_indentation, label, _newline,]) => label)),
|
|
906
|
+
smaliIndentationParser,
|
|
907
|
+
createExactSequenceParser('.end '),
|
|
908
|
+
createExactSequenceParser(operationName),
|
|
909
|
+
smaliLineEndPraser,
|
|
910
|
+
]), ([labels,]) => labels);
|
|
911
|
+
const createMultilineSmaliCodeOperationLabelMapBodyParser = operationName => promiseCompose(createTupleParser([
|
|
912
|
+
createArrayParser(promiseCompose(createTupleParser([
|
|
913
|
+
smaliIndentationParser,
|
|
914
|
+
smaliParametersIntegerParser,
|
|
915
|
+
createExactSequenceParser(' -> '),
|
|
916
|
+
smaliCodeLabelParser,
|
|
917
|
+
smaliLineEndPraser,
|
|
918
|
+
]), ([_indentation, key, _arrow, label, _newline,]) => [
|
|
919
|
+
key,
|
|
920
|
+
label,
|
|
921
|
+
])),
|
|
922
|
+
smaliIndentationParser,
|
|
923
|
+
createExactSequenceParser('.end '),
|
|
924
|
+
createExactSequenceParser(operationName),
|
|
925
|
+
smaliLineEndPraser,
|
|
926
|
+
]), ([labels,]) => labels);
|
|
927
|
+
const createMultilineSmaliCodeOperationIntegersBodyParser = operationName => promiseCompose(createTupleParser([
|
|
928
|
+
createArrayParser(promiseCompose(createTupleParser([
|
|
929
|
+
smaliIndentationParser,
|
|
930
|
+
smaliParametersIntegerParser,
|
|
931
|
+
smaliLineEndPraser,
|
|
932
|
+
]), ([_indentation, key,]) => key)),
|
|
933
|
+
smaliIndentationParser,
|
|
934
|
+
createExactSequenceParser('.end '),
|
|
935
|
+
createExactSequenceParser(operationName),
|
|
936
|
+
smaliLineEndPraser,
|
|
937
|
+
]), ([labels,]) => labels);
|
|
938
|
+
const createMultilineSmaliCodeOperationBodyParser = operationName => createUnionParser([
|
|
939
|
+
createMultilineSmaliCodeOperationLabelsBodyParser(operationName),
|
|
940
|
+
createMultilineSmaliCodeOperationIntegersBodyParser(operationName),
|
|
941
|
+
createMultilineSmaliCodeOperationLabelMapBodyParser(operationName),
|
|
452
942
|
]);
|
|
453
|
-
const
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
943
|
+
const smaliMultilineCodeOperationParser = parserCreatorCompose(() => promiseCompose(createTupleParser([
|
|
944
|
+
smaliSingleIndentationParser,
|
|
945
|
+
createExactSequenceParser('.'),
|
|
946
|
+
smaliCodeOperationNameParser,
|
|
947
|
+
promiseCompose(createOptionalParser(createTupleParser([
|
|
948
|
+
smaliSingleWhitespaceParser,
|
|
949
|
+
smaliCodeOperationParametersParser,
|
|
950
|
+
])), undefinedOrParameters => undefinedOrParameters === undefined ? [] : undefinedOrParameters[1]),
|
|
951
|
+
smaliLineEndPraser,
|
|
952
|
+
]), ([_indent, _dot, operation, parameters, _newline,]) => ({
|
|
953
|
+
operation,
|
|
954
|
+
parameters,
|
|
955
|
+
})), ({ operation, parameters }) => promiseCompose(createMultilineSmaliCodeOperationBodyParser(operation), body => ({
|
|
956
|
+
operation,
|
|
957
|
+
parameters,
|
|
958
|
+
body,
|
|
959
|
+
})))();
|
|
960
|
+
setParserName(smaliMultilineCodeOperationParser, 'smaliMultilineCodeOperationParser');
|
|
961
|
+
const smaliLooseCodeOperationParser = createUnionParser([
|
|
962
|
+
smaliOneLineCodeOperationParser,
|
|
963
|
+
smaliMultilineCodeOperationParser,
|
|
964
|
+
]);
|
|
965
|
+
setParserName(smaliLooseCodeOperationParser, 'smaliLooseCodeOperationParser');
|
|
966
|
+
export const smaliCodeOperationParser = promiseCompose(smaliLooseCodeOperationParser, operation => {
|
|
459
967
|
const operation_ = {
|
|
460
968
|
operation: operation.operation,
|
|
461
|
-
//
|
|
969
|
+
// Line,
|
|
462
970
|
// local,
|
|
463
|
-
// label,
|
|
464
971
|
// parameters: (operation as any).parameters,
|
|
465
972
|
}; // TODO
|
|
973
|
+
if ('body' in operation) {
|
|
974
|
+
const operationDexName = payloadOperationSmaliNameToDexName.get(operation.operation);
|
|
975
|
+
invariant(operationDexName, 'Unknown payload operation for %s', operation.operation);
|
|
976
|
+
operation_.operation = operationDexName;
|
|
977
|
+
if (operationDexName === 'sparse-switch-payload') {
|
|
978
|
+
const map = new Map(operation.body);
|
|
979
|
+
operation_.keys = [...map.keys()];
|
|
980
|
+
operation_.branchLabels = [...map.values()];
|
|
981
|
+
}
|
|
982
|
+
else if (operationDexName === 'fill-array-data-payload') {
|
|
983
|
+
// Get elementWidth from parameters (first parameter)
|
|
984
|
+
const elementWidth = operation.parameters[0];
|
|
985
|
+
invariant(typeof elementWidth === 'number', 'Expected elementWidth to be a number');
|
|
986
|
+
operation_.elementWidth = elementWidth;
|
|
987
|
+
// Convert integer values to bytes (little-endian)
|
|
988
|
+
const values = operation.body;
|
|
989
|
+
const data = [];
|
|
990
|
+
for (const value of values) {
|
|
991
|
+
// Use BigInt for bitwise operations to preserve full precision
|
|
992
|
+
const bigIntValue = typeof value === 'bigint' ? value : BigInt(value);
|
|
993
|
+
// Convert to bytes based on elementWidth (little-endian)
|
|
994
|
+
for (let i = 0; i < elementWidth; i++) {
|
|
995
|
+
data.push(Number((bigIntValue >> BigInt(i * 8)) & 0xffn));
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
operation_.data = data;
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
operation_.branchLabels = operation.body;
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
const hasRegisters = Array.isArray(operation.parameters.at(0));
|
|
1005
|
+
if (hasRegisters) {
|
|
1006
|
+
operation_.registers = [];
|
|
1007
|
+
}
|
|
466
1008
|
for (const parameter of operation.parameters.flat() ?? []) {
|
|
467
1009
|
if (isDalvikExecutableMethod(parameter)) {
|
|
468
1010
|
operation_.method = parameter;
|
|
@@ -473,9 +1015,7 @@ const smaliAnnotatedCodeOperationParser = promiseCompose(createTupleParser([
|
|
|
473
1015
|
continue;
|
|
474
1016
|
}
|
|
475
1017
|
if (isSmaliRegister(parameter)) {
|
|
476
|
-
|
|
477
|
-
operation_.registers = [];
|
|
478
|
-
}
|
|
1018
|
+
operation_.registers ||= [];
|
|
479
1019
|
operation_.registers.push(parameter);
|
|
480
1020
|
continue;
|
|
481
1021
|
}
|
|
@@ -484,97 +1024,678 @@ const smaliAnnotatedCodeOperationParser = promiseCompose(createTupleParser([
|
|
|
484
1024
|
operation_.type = parameter;
|
|
485
1025
|
continue;
|
|
486
1026
|
}
|
|
1027
|
+
if (operationsWithBranchLabelArgument.has(operation_.operation)) {
|
|
1028
|
+
operation_.branchLabel = parameter;
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
487
1031
|
operation_.string = parameter;
|
|
488
1032
|
continue;
|
|
489
1033
|
}
|
|
490
1034
|
if (typeof parameter === 'number' || typeof parameter === 'bigint') {
|
|
491
|
-
|
|
1035
|
+
// Skip for fill-array-data-payload, already handled in body processing
|
|
1036
|
+
if (operation_.operation === 'fill-array-data-payload') {
|
|
1037
|
+
continue;
|
|
1038
|
+
}
|
|
1039
|
+
// Const-wide operations always use bigint values
|
|
1040
|
+
if (operationsWithBigintValue.has(operation_.operation)) {
|
|
1041
|
+
operation_.value = typeof parameter === 'number' ? BigInt(parameter) : parameter;
|
|
1042
|
+
}
|
|
1043
|
+
else {
|
|
1044
|
+
operation_.value = parameter;
|
|
1045
|
+
}
|
|
492
1046
|
continue;
|
|
493
1047
|
}
|
|
494
1048
|
invariant(false, 'TODO: parameter: %s, operation: %s', JSON.stringify(parameter), JSON.stringify(operation));
|
|
495
1049
|
}
|
|
496
|
-
if (operation_.registers) {
|
|
497
|
-
operation_.registers = operation_.registers.reverse();
|
|
498
|
-
}
|
|
499
1050
|
return operation_;
|
|
500
1051
|
});
|
|
501
1052
|
setParserName(smaliCodeOperationParser, 'smaliCodeOperationParser');
|
|
1053
|
+
const operationsWithTypeArgument = new Set([
|
|
1054
|
+
'new-instance',
|
|
1055
|
+
'new-array',
|
|
1056
|
+
'filled-new-array',
|
|
1057
|
+
'filled-new-array/range',
|
|
1058
|
+
'check-cast',
|
|
1059
|
+
'instance-of',
|
|
1060
|
+
'const-class',
|
|
1061
|
+
]);
|
|
1062
|
+
const operationsWithBigintValue = new Set([
|
|
1063
|
+
'const-wide/16',
|
|
1064
|
+
'const-wide/32',
|
|
1065
|
+
'const-wide',
|
|
1066
|
+
]);
|
|
1067
|
+
const operationsWithBranchLabelArgument = new Set([
|
|
1068
|
+
'goto',
|
|
1069
|
+
'goto/16',
|
|
1070
|
+
'goto/32',
|
|
1071
|
+
'if-eq',
|
|
1072
|
+
'if-ne',
|
|
1073
|
+
'if-lt',
|
|
1074
|
+
'if-ge',
|
|
1075
|
+
'if-gt',
|
|
1076
|
+
'if-le',
|
|
1077
|
+
'if-eqz',
|
|
1078
|
+
'if-nez',
|
|
1079
|
+
'if-ltz',
|
|
1080
|
+
'if-gez',
|
|
1081
|
+
'if-gtz',
|
|
1082
|
+
'if-lez',
|
|
1083
|
+
'packed-switch',
|
|
1084
|
+
'sparse-switch',
|
|
1085
|
+
'fill-array-data',
|
|
1086
|
+
]);
|
|
1087
|
+
const payloadOperationSmaliNameToDexName = new Map(Object.entries({
|
|
1088
|
+
'packed-switch': 'packed-switch-payload',
|
|
1089
|
+
'sparse-switch': 'sparse-switch-payload',
|
|
1090
|
+
'array-data': 'fill-array-data-payload',
|
|
1091
|
+
}));
|
|
1092
|
+
function isOperationWithLabels(value) {
|
|
1093
|
+
return (typeof value === 'object'
|
|
1094
|
+
&& value !== null
|
|
1095
|
+
&& 'labels' in value
|
|
1096
|
+
&& value.labels instanceof Set);
|
|
1097
|
+
}
|
|
1098
|
+
const smaliAnnotatedCodeOperationParser = promiseCompose(createTupleParser([
|
|
1099
|
+
createArrayParser(smaliCodeLineParser),
|
|
1100
|
+
createOptionalParser(smaliCodeLocalParser),
|
|
1101
|
+
createOptionalParser(smaliCodeEndLocalParser),
|
|
1102
|
+
createOptionalParser(smaliCodeRestartLocalParser),
|
|
1103
|
+
createOptionalParser(smaliCodePrologueEndParser),
|
|
1104
|
+
createOptionalParser(smaliCodeEpilogueBeginParser),
|
|
1105
|
+
createArrayParser(smaliCodeLabelLineParser),
|
|
1106
|
+
smaliCodeOperationParser,
|
|
1107
|
+
]), ([lines, local, endLocal, restartLocal, prologueEnd, epilogueBegin, labels, operation,]) => {
|
|
1108
|
+
const debugDirectives = [];
|
|
1109
|
+
for (const line of lines) {
|
|
1110
|
+
debugDirectives.push({ type: 'line', line });
|
|
1111
|
+
}
|
|
1112
|
+
if (local) {
|
|
1113
|
+
debugDirectives.push({ type: 'startLocal', local });
|
|
1114
|
+
}
|
|
1115
|
+
if (endLocal) {
|
|
1116
|
+
debugDirectives.push({ type: 'endLocal', register: endLocal });
|
|
1117
|
+
}
|
|
1118
|
+
if (restartLocal) {
|
|
1119
|
+
debugDirectives.push({ type: 'restartLocal', register: restartLocal });
|
|
1120
|
+
}
|
|
1121
|
+
if (prologueEnd) {
|
|
1122
|
+
debugDirectives.push({ type: 'setPrologueEnd' });
|
|
1123
|
+
}
|
|
1124
|
+
if (epilogueBegin) {
|
|
1125
|
+
debugDirectives.push({ type: 'setEpilogueBegin' });
|
|
1126
|
+
}
|
|
1127
|
+
return {
|
|
1128
|
+
debugDirectives,
|
|
1129
|
+
labels,
|
|
1130
|
+
operation,
|
|
1131
|
+
};
|
|
1132
|
+
});
|
|
1133
|
+
setParserName(smaliAnnotatedCodeOperationParser, 'smaliAnnotatedCodeOperationParser');
|
|
502
1134
|
const smaliExecutableCodeParser = promiseCompose(createTupleParser([
|
|
503
|
-
smaliCodeRegistersParser,
|
|
1135
|
+
createOptionalParser(smaliCodeRegistersParser),
|
|
504
1136
|
createArrayParser(smaliAnnotationParser),
|
|
505
|
-
|
|
1137
|
+
createArrayParser(smaliCodeParameterParser),
|
|
1138
|
+
createOptionalParser(smaliCommentsOrNewlinesParser),
|
|
1139
|
+
createSeparatedArrayParser(smaliAnnotationParser, smaliCommentsOrNewlinesParser),
|
|
506
1140
|
createArrayParser(promiseCompose(createTupleParser([
|
|
507
1141
|
createOptionalParser(smaliCommentsOrNewlinesParser),
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
1142
|
+
createDisjunctionParser([
|
|
1143
|
+
smaliIndentedCommentParser,
|
|
1144
|
+
smaliAnnotatedCodeOperationParser,
|
|
1145
|
+
smaliLabeledCatchDirectiveParser,
|
|
1146
|
+
]),
|
|
1147
|
+
]), ([_commentsOrNewlinesParser, operation,]) => operation)),
|
|
1148
|
+
]), ([registersSize, annotations1, parameters, _leadingCommentsOrNewlines, annotations2, instructionsAndCatchDirectives,]) => {
|
|
1149
|
+
const annotations = [...annotations1, ...annotations2];
|
|
1150
|
+
const instructions = [];
|
|
1151
|
+
const annotatedOperations = [];
|
|
1152
|
+
const catchDirectives = [];
|
|
1153
|
+
const catchDirectiveLabels = new Map();
|
|
1154
|
+
for (const item of instructionsAndCatchDirectives) {
|
|
1155
|
+
if (item && typeof item === 'object') {
|
|
1156
|
+
if ('labels' in item && 'catchDirective' in item) {
|
|
1157
|
+
// This is a SmaliLabeledCatchDirective
|
|
1158
|
+
const labeledCatch = item;
|
|
1159
|
+
catchDirectives.push(labeledCatch.catchDirective);
|
|
1160
|
+
catchDirectiveLabels.set(labeledCatch.catchDirective, labeledCatch.labels);
|
|
1161
|
+
}
|
|
1162
|
+
else if ('type' in item && 'startLabel' in item && 'endLabel' in item && 'handlerLabel' in item) {
|
|
1163
|
+
// This is a bare SmaliCatchDirective (shouldn't happen with current parser structure)
|
|
1164
|
+
catchDirectives.push(item);
|
|
1165
|
+
}
|
|
1166
|
+
else if ('debugDirectives' in item && 'labels' in item && 'operation' in item) {
|
|
1167
|
+
// This is a SmaliAnnotatedCodeOperation
|
|
1168
|
+
const annotated = item;
|
|
1169
|
+
annotatedOperations.push(annotated);
|
|
1170
|
+
const operation = annotated.operation;
|
|
1171
|
+
if (annotated.labels.length > 0) {
|
|
1172
|
+
operation.labels = new Set(annotated.labels);
|
|
1173
|
+
}
|
|
1174
|
+
instructions.push(operation);
|
|
1175
|
+
}
|
|
1176
|
+
else if (item !== undefined) {
|
|
1177
|
+
// This is a SmaliCodeOperation (fallback)
|
|
1178
|
+
instructions.push(item);
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
if (registersSize === undefined
|
|
1183
|
+
&& parameters !== undefined) {
|
|
1184
|
+
registersSize = parameters.length;
|
|
1185
|
+
}
|
|
1186
|
+
invariant(registersSize !== undefined, 'Expected registers size to be defined');
|
|
1187
|
+
for (const [operationIndex, operation] of instructions.entries()) {
|
|
1188
|
+
if ('branchLabel' in operation
|
|
1189
|
+
&& typeof operation.branchLabel === 'string') {
|
|
1190
|
+
for (const [targetOperationIndex, targetOperation] of instructions.entries()) {
|
|
1191
|
+
if (!isOperationWithLabels(targetOperation)) {
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
if (!targetOperation.labels.has(operation.branchLabel)) {
|
|
1195
|
+
continue;
|
|
1196
|
+
}
|
|
1197
|
+
delete operation.branchLabel;
|
|
1198
|
+
operation.branchOffsetIndex = targetOperationIndex - operationIndex;
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if ('branchLabels' in operation
|
|
1203
|
+
&& typeof operation.branchLabels === 'object'
|
|
1204
|
+
&& Array.isArray(operation.branchLabels)) {
|
|
1205
|
+
const sourceOperations = instructions.filter((sourceOperation, sourceOperationIndex) => ('branchOffsetIndex' in sourceOperation
|
|
1206
|
+
&& typeof sourceOperation.branchOffsetIndex === 'number'
|
|
1207
|
+
&& sourceOperation.branchOffsetIndex + sourceOperationIndex === operationIndex));
|
|
1208
|
+
invariant(sourceOperations.length === 1, 'Expected exactly one source operation to point to %s', JSON.stringify(operation));
|
|
1209
|
+
const [sourceOperation] = sourceOperations;
|
|
1210
|
+
const sourceOperationIndex = instructions.indexOf(sourceOperation);
|
|
1211
|
+
operation.branchOffsetIndices = operation.branchLabels.map((branchLabel) => {
|
|
1212
|
+
for (const [targetOperationIndex, targetOperation] of instructions.entries()) {
|
|
1213
|
+
if (!isOperationWithLabels(targetOperation)) {
|
|
1214
|
+
continue;
|
|
1215
|
+
}
|
|
1216
|
+
if (!targetOperation.labels.has(branchLabel)) {
|
|
1217
|
+
continue;
|
|
1218
|
+
}
|
|
1219
|
+
return targetOperationIndex - sourceOperationIndex;
|
|
1220
|
+
}
|
|
1221
|
+
invariant(false, 'Expected to find branch label %s', branchLabel);
|
|
1222
|
+
});
|
|
1223
|
+
delete operation.branchLabels;
|
|
1224
|
+
}
|
|
1225
|
+
}
|
|
1226
|
+
const branchOffsetByBranchOffsetIndex = new Map();
|
|
1227
|
+
let operationOffset = 0;
|
|
1228
|
+
for (const [operationIndex, operation] of instructions.entries()) {
|
|
1229
|
+
const operationSize = getOperationFormatSize(operation);
|
|
1230
|
+
branchOffsetByBranchOffsetIndex.set(operationIndex, operationOffset);
|
|
1231
|
+
operationOffset += operationSize;
|
|
1232
|
+
}
|
|
1233
|
+
for (const [operationIndex, operation] of instructions.entries()) {
|
|
1234
|
+
if ('branchOffsetIndex' in operation
|
|
1235
|
+
&& typeof operation.branchOffsetIndex === 'number') {
|
|
1236
|
+
const operationOffset = branchOffsetByBranchOffsetIndex.get(operationIndex + operation.branchOffsetIndex);
|
|
1237
|
+
invariant(operationOffset !== undefined, 'Expected branch offset for operation index %s, but got undefined', operation.branchOffsetIndex);
|
|
1238
|
+
const branchOffset = branchOffsetByBranchOffsetIndex.get(operationIndex);
|
|
1239
|
+
invariant(branchOffset !== undefined, 'Expected branch offset for operation index %s, but got undefined', operationIndex);
|
|
1240
|
+
operation.branchOffset = operationOffset - branchOffset;
|
|
1241
|
+
}
|
|
1242
|
+
if ('branchOffsetIndices' in operation
|
|
1243
|
+
&& typeof operation.branchOffsetIndices === 'object'
|
|
1244
|
+
&& Array.isArray(operation.branchOffsetIndices)) {
|
|
1245
|
+
const sourceOperations = instructions.filter((sourceOperation, sourceOperationIndex) => ('branchOffsetIndex' in sourceOperation
|
|
1246
|
+
&& typeof sourceOperation.branchOffsetIndex === 'number'
|
|
1247
|
+
&& sourceOperation.branchOffsetIndex + sourceOperationIndex === operationIndex));
|
|
1248
|
+
invariant(sourceOperations.length === 1, 'Expected exactly one source operation to point to %s', JSON.stringify(operation));
|
|
1249
|
+
const [sourceOperation] = sourceOperations;
|
|
1250
|
+
const sourceOperationIndex = instructions.indexOf(sourceOperation);
|
|
1251
|
+
operation.branchOffsets = operation.branchOffsetIndices.map((branchOffsetIndex) => {
|
|
1252
|
+
const operationOffset = branchOffsetByBranchOffsetIndex.get(sourceOperationIndex + branchOffsetIndex);
|
|
1253
|
+
invariant(operationOffset !== undefined, 'Expected branch offset for operation index %s, but got undefined', sourceOperationIndex + branchOffsetIndex);
|
|
1254
|
+
const branchOffset = branchOffsetByBranchOffsetIndex.get(sourceOperationIndex);
|
|
1255
|
+
invariant(branchOffset !== undefined, 'Expected branch offset for operation index %s, but got undefined', sourceOperationIndex);
|
|
1256
|
+
return operationOffset - branchOffset;
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
// Build label-to-index mapping
|
|
1261
|
+
// Labels attached to instructions map to that instruction's index
|
|
1262
|
+
// Labels before catch directives should map to the position they mark
|
|
1263
|
+
const labelToIndexMap = new Map();
|
|
1264
|
+
// First, map labels from instructions
|
|
1265
|
+
for (const [operationIndex, operation] of instructions.entries()) {
|
|
1266
|
+
if (operation
|
|
1267
|
+
&& typeof operation === 'object'
|
|
1268
|
+
&& 'labels' in operation
|
|
1269
|
+
&& operation.labels instanceof Set) {
|
|
1270
|
+
for (const label of operation.labels) {
|
|
1271
|
+
labelToIndexMap.set(label, operationIndex);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
// Now handle labels from catch directives
|
|
1276
|
+
// We need to figure out where each catch directive appears in the original sequence
|
|
1277
|
+
let instructionArrayIndex = 0;
|
|
1278
|
+
for (let i = 0; i < instructionsAndCatchDirectives.length; i++) {
|
|
1279
|
+
const item = instructionsAndCatchDirectives[i];
|
|
1280
|
+
if (item && typeof item === 'object' && 'labels' in item && 'catchDirective' in item) {
|
|
1281
|
+
// This is a catch directive with labels
|
|
1282
|
+
// These labels should map to the current instruction index
|
|
1283
|
+
// (which is where the next instruction would be)
|
|
1284
|
+
const labeledCatch = item;
|
|
1285
|
+
for (const label of labeledCatch.labels) {
|
|
1286
|
+
labelToIndexMap.set(label, instructionArrayIndex);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
else if (item !== undefined && !(item && typeof item === 'object' && 'type' in item && 'startLabel' in item)) {
|
|
1290
|
+
// This is an instruction, increment the counter
|
|
1291
|
+
instructionArrayIndex++;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
// Build tries array from catch directives
|
|
1295
|
+
const triesByRange = new Map();
|
|
1296
|
+
for (const catchDirective of catchDirectives) {
|
|
1297
|
+
// Find the start and end instruction indices
|
|
1298
|
+
const startIndex = labelToIndexMap.get(catchDirective.startLabel);
|
|
1299
|
+
const endIndex = labelToIndexMap.get(catchDirective.endLabel);
|
|
1300
|
+
const handlerIndex = labelToIndexMap.get(catchDirective.handlerLabel);
|
|
1301
|
+
invariant(startIndex !== undefined, 'Expected to find start label %s', catchDirective.startLabel);
|
|
1302
|
+
invariant(endIndex !== undefined, 'Expected to find end label %s', catchDirective.endLabel);
|
|
1303
|
+
invariant(handlerIndex !== undefined, 'Expected to find handler label %s', catchDirective.handlerLabel);
|
|
1304
|
+
const startAddress = branchOffsetByBranchOffsetIndex.get(startIndex);
|
|
1305
|
+
const endAddress = branchOffsetByBranchOffsetIndex.get(endIndex);
|
|
1306
|
+
const handlerAddress = branchOffsetByBranchOffsetIndex.get(handlerIndex);
|
|
1307
|
+
invariant(startAddress !== undefined, 'Expected start address for index %s', startIndex);
|
|
1308
|
+
invariant(endAddress !== undefined, 'Expected end address for index %s', endIndex);
|
|
1309
|
+
invariant(handlerAddress !== undefined, 'Expected handler address for index %s', handlerIndex);
|
|
1310
|
+
const instructionCount = endAddress - startAddress;
|
|
1311
|
+
const rangeKey = `${startAddress}-${instructionCount}`;
|
|
1312
|
+
let tryEntry = triesByRange.get(rangeKey);
|
|
1313
|
+
if (!tryEntry) {
|
|
1314
|
+
tryEntry = {
|
|
1315
|
+
startAddress,
|
|
1316
|
+
instructionCount,
|
|
1317
|
+
handlers: [],
|
|
1318
|
+
catchAllAddress: undefined,
|
|
1319
|
+
};
|
|
1320
|
+
triesByRange.set(rangeKey, tryEntry);
|
|
1321
|
+
}
|
|
1322
|
+
if (catchDirective.type === undefined) {
|
|
1323
|
+
// .catchall
|
|
1324
|
+
tryEntry.catchAllAddress = handlerAddress;
|
|
1325
|
+
}
|
|
1326
|
+
else {
|
|
1327
|
+
// .catch Type
|
|
1328
|
+
tryEntry.handlers.push({
|
|
1329
|
+
type: catchDirective.type,
|
|
1330
|
+
address: handlerAddress,
|
|
1331
|
+
});
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
const tries = Array.from(triesByRange.values()).map(tryEntry => ({
|
|
1335
|
+
startAddress: tryEntry.startAddress,
|
|
1336
|
+
instructionCount: tryEntry.instructionCount,
|
|
1337
|
+
handler: {
|
|
1338
|
+
handlers: tryEntry.handlers,
|
|
1339
|
+
catchAllAddress: tryEntry.catchAllAddress,
|
|
1340
|
+
},
|
|
1341
|
+
}));
|
|
1342
|
+
for (const operation of instructions) {
|
|
1343
|
+
delete operation.labels;
|
|
1344
|
+
delete operation.branchOffsetIndex;
|
|
1345
|
+
delete operation.branchOffsetIndices;
|
|
1346
|
+
}
|
|
1347
|
+
let debugInfo;
|
|
1348
|
+
const debugBytecode = [];
|
|
1349
|
+
let currentLine;
|
|
1350
|
+
let lineStart;
|
|
1351
|
+
// Helper to convert SmaliRegister to register number
|
|
1352
|
+
const getRegisterNum = (register) => {
|
|
1353
|
+
// In Smali, 'v' registers start from 0, 'p' registers are parameters
|
|
1354
|
+
// For now, we'll just use the index. The actual conversion may need
|
|
1355
|
+
// to account for registersSize and parameter mapping.
|
|
1356
|
+
if (register.prefix === 'v') {
|
|
1357
|
+
return register.index;
|
|
1358
|
+
}
|
|
1359
|
+
else {
|
|
1360
|
+
// 'p' registers: need to map to actual register numbers
|
|
1361
|
+
// p0 starts at (registersSize - insSize)
|
|
1362
|
+
// For now, just use the index as we don't have accurate insSize
|
|
1363
|
+
return register.index;
|
|
1364
|
+
}
|
|
1365
|
+
};
|
|
1366
|
+
// Helper to emit accumulated line/address changes as special opcode or explicit opcodes
|
|
1367
|
+
const emitPendingChanges = (lineDiff, addressDiff) => {
|
|
1368
|
+
// Special opcode formula: opcode = 0x0A + (line_diff + 4) + 15 * addr_diff
|
|
1369
|
+
// Valid when: line_diff ∈ [-4, 10] and addr_diff ∈ [0, 17] and result ≤ 0xFF
|
|
1370
|
+
if (lineDiff >= -4 && lineDiff <= 10 && addressDiff >= 0) {
|
|
1371
|
+
const adjusted = (lineDiff + 4) + 15 * addressDiff;
|
|
1372
|
+
const opcode = 0x0A + adjusted;
|
|
1373
|
+
if (opcode <= 0xFF) {
|
|
1374
|
+
debugBytecode.push({
|
|
1375
|
+
type: 'special',
|
|
1376
|
+
value: opcode,
|
|
1377
|
+
});
|
|
1378
|
+
return;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
// Fall back to explicit opcodes
|
|
1382
|
+
if (addressDiff > 0) {
|
|
1383
|
+
debugBytecode.push({
|
|
1384
|
+
type: 'advancePc',
|
|
1385
|
+
addressDiff,
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
if (lineDiff !== 0) {
|
|
1389
|
+
debugBytecode.push({
|
|
1390
|
+
type: 'advanceLine',
|
|
1391
|
+
lineDiff,
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
};
|
|
1395
|
+
let accumulatedAddressDiff = 0;
|
|
1396
|
+
let accumulatedLineDiff = 0;
|
|
1397
|
+
let lastAddress = 0;
|
|
1398
|
+
let hasEmittedFirstDebugEvent = false;
|
|
1399
|
+
for (let i = 0; i < annotatedOperations.length; i++) {
|
|
1400
|
+
const annotated = annotatedOperations[i];
|
|
1401
|
+
const operationAddress = branchOffsetByBranchOffsetIndex.get(i) ?? 0;
|
|
1402
|
+
// Accumulate address difference (only after we've started tracking)
|
|
1403
|
+
if (i > 0 && hasEmittedFirstDebugEvent) {
|
|
1404
|
+
accumulatedAddressDiff += operationAddress - lastAddress;
|
|
1405
|
+
}
|
|
1406
|
+
lastAddress = operationAddress;
|
|
1407
|
+
// Process line directives first to update accumulatedLineDiff
|
|
1408
|
+
for (const directive of annotated.debugDirectives) {
|
|
1409
|
+
if (directive.type === 'line') {
|
|
1410
|
+
if (lineStart === undefined) {
|
|
1411
|
+
lineStart = directive.line;
|
|
1412
|
+
currentLine = directive.line;
|
|
1413
|
+
}
|
|
1414
|
+
else if (currentLine !== undefined) {
|
|
1415
|
+
accumulatedLineDiff += directive.line - currentLine;
|
|
1416
|
+
currentLine = directive.line;
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
}
|
|
1420
|
+
// Emit accumulated changes before any other debug events
|
|
1421
|
+
const hasOtherDebugEvents = annotated.debugDirectives.some(d => d.type !== 'line');
|
|
1422
|
+
if (hasOtherDebugEvents && (accumulatedLineDiff !== 0 || accumulatedAddressDiff !== 0 || !hasEmittedFirstDebugEvent)) {
|
|
1423
|
+
emitPendingChanges(accumulatedLineDiff, accumulatedAddressDiff);
|
|
1424
|
+
accumulatedLineDiff = 0;
|
|
1425
|
+
accumulatedAddressDiff = 0;
|
|
1426
|
+
hasEmittedFirstDebugEvent = true;
|
|
1427
|
+
}
|
|
1428
|
+
// Emit other debug directives
|
|
1429
|
+
for (const directive of annotated.debugDirectives) {
|
|
1430
|
+
if (directive.type === 'startLocal') {
|
|
1431
|
+
debugBytecode.push({
|
|
1432
|
+
type: 'startLocal',
|
|
1433
|
+
registerNum: getRegisterNum(directive.local.register),
|
|
1434
|
+
name: directive.local.name,
|
|
1435
|
+
type_: directive.local.type,
|
|
1436
|
+
});
|
|
1437
|
+
}
|
|
1438
|
+
else if (directive.type === 'endLocal') {
|
|
1439
|
+
debugBytecode.push({
|
|
1440
|
+
type: 'endLocal',
|
|
1441
|
+
registerNum: getRegisterNum(directive.register),
|
|
1442
|
+
});
|
|
1443
|
+
}
|
|
1444
|
+
else if (directive.type === 'restartLocal') {
|
|
1445
|
+
debugBytecode.push({
|
|
1446
|
+
type: 'restartLocal',
|
|
1447
|
+
registerNum: getRegisterNum(directive.register),
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
else if (directive.type === 'setPrologueEnd') {
|
|
1451
|
+
debugBytecode.push({
|
|
1452
|
+
type: 'setPrologueEnd',
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
else if (directive.type === 'setEpilogueBegin') {
|
|
1456
|
+
debugBytecode.push({
|
|
1457
|
+
type: 'setEpilogueBegin',
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
// Emit any remaining accumulated changes at the end
|
|
1463
|
+
if (accumulatedLineDiff !== 0 || accumulatedAddressDiff !== 0) {
|
|
1464
|
+
emitPendingChanges(accumulatedLineDiff, accumulatedAddressDiff);
|
|
1465
|
+
}
|
|
1466
|
+
if (lineStart !== undefined) {
|
|
1467
|
+
// Extract parameter names from .param directives
|
|
1468
|
+
const parameterNames = [];
|
|
1469
|
+
// TODO: Extract from parameters array if needed
|
|
1470
|
+
// If we have a lineStart but no bytecode, we need to emit a special opcode
|
|
1471
|
+
// to establish the initial state. The special value 0x0E (14) means:
|
|
1472
|
+
// adjusted = 14 - 10 = 4, line_diff = 4 % 15 - 4 = 0, pc_diff = 4 / 15 = 0
|
|
1473
|
+
if (debugBytecode.length === 0) {
|
|
1474
|
+
debugBytecode.push({
|
|
1475
|
+
type: 'special',
|
|
1476
|
+
value: 0x0A + 4, // 14 in decimal
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
debugInfo = {
|
|
1480
|
+
lineStart,
|
|
1481
|
+
parameterNames,
|
|
1482
|
+
bytecode: debugBytecode,
|
|
1483
|
+
};
|
|
1484
|
+
}
|
|
511
1485
|
return {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
1486
|
+
dalvikExecutableCode: {
|
|
1487
|
+
registersSize,
|
|
1488
|
+
insSize: -1, // TODO
|
|
1489
|
+
outsSize: -1, // TODO
|
|
1490
|
+
debugInfo,
|
|
1491
|
+
instructions: instructions, // TODO
|
|
1492
|
+
tries,
|
|
1493
|
+
// _annotations,
|
|
1494
|
+
},
|
|
1495
|
+
parameters,
|
|
1496
|
+
parameterAnnotations: parameters.filter(parameter => parameter.annotation !== undefined),
|
|
1497
|
+
methodAnnotations: annotations,
|
|
519
1498
|
};
|
|
520
1499
|
});
|
|
521
1500
|
setParserName(smaliExecutableCodeParser, 'smaliExecutableCodeParser');
|
|
522
|
-
|
|
1501
|
+
// TODO: ???
|
|
1502
|
+
const sortRegistersOperations = new Set([
|
|
1503
|
+
'if-eq',
|
|
1504
|
+
'if-ne',
|
|
1505
|
+
]);
|
|
1506
|
+
// All 12x format operations need register reversal because:
|
|
1507
|
+
// - Bytecode format is B|A (B in high nibble, A in low nibble)
|
|
1508
|
+
// - nibblesParser returns [B, A]
|
|
1509
|
+
// - Smali syntax is "op vA, vB" (destination first)
|
|
1510
|
+
// - So we need to reverse to get [A, B]
|
|
1511
|
+
const reverseRegistersOperations = new Set([
|
|
1512
|
+
// Move operations
|
|
1513
|
+
'move',
|
|
1514
|
+
'move-wide',
|
|
1515
|
+
'move-object',
|
|
1516
|
+
// Unary operations (negation, bitwise not)
|
|
1517
|
+
'neg-int',
|
|
1518
|
+
'not-int',
|
|
1519
|
+
'neg-long',
|
|
1520
|
+
'not-long',
|
|
1521
|
+
'neg-float',
|
|
1522
|
+
'neg-double',
|
|
1523
|
+
// Type conversion operations
|
|
1524
|
+
'int-to-long',
|
|
1525
|
+
'int-to-float',
|
|
1526
|
+
'int-to-double',
|
|
1527
|
+
'long-to-int',
|
|
1528
|
+
'long-to-float',
|
|
1529
|
+
'long-to-double',
|
|
1530
|
+
'float-to-int',
|
|
1531
|
+
'float-to-long',
|
|
1532
|
+
'float-to-double',
|
|
1533
|
+
'double-to-int',
|
|
1534
|
+
'double-to-long',
|
|
1535
|
+
'double-to-float',
|
|
1536
|
+
'int-to-byte',
|
|
1537
|
+
'int-to-char',
|
|
1538
|
+
'int-to-short',
|
|
1539
|
+
// Binary operations with /2addr suffix
|
|
1540
|
+
'add-int/2addr',
|
|
1541
|
+
'sub-int/2addr',
|
|
1542
|
+
'mul-int/2addr',
|
|
1543
|
+
'div-int/2addr',
|
|
1544
|
+
'rem-int/2addr',
|
|
1545
|
+
'and-int/2addr',
|
|
1546
|
+
'or-int/2addr',
|
|
1547
|
+
'xor-int/2addr',
|
|
1548
|
+
'shl-int/2addr',
|
|
1549
|
+
'shr-int/2addr',
|
|
1550
|
+
'ushr-int/2addr',
|
|
1551
|
+
'add-long/2addr',
|
|
1552
|
+
'sub-long/2addr',
|
|
1553
|
+
'mul-long/2addr',
|
|
1554
|
+
'div-long/2addr',
|
|
1555
|
+
'rem-long/2addr',
|
|
1556
|
+
'and-long/2addr',
|
|
1557
|
+
'or-long/2addr',
|
|
1558
|
+
'xor-long/2addr',
|
|
1559
|
+
'shl-long/2addr',
|
|
1560
|
+
'shr-long/2addr',
|
|
1561
|
+
'ushr-long/2addr',
|
|
1562
|
+
'add-float/2addr',
|
|
1563
|
+
'sub-float/2addr',
|
|
1564
|
+
'mul-float/2addr',
|
|
1565
|
+
'div-float/2addr',
|
|
1566
|
+
'rem-float/2addr',
|
|
1567
|
+
'add-double/2addr',
|
|
1568
|
+
'sub-double/2addr',
|
|
1569
|
+
'mul-double/2addr',
|
|
1570
|
+
'div-double/2addr',
|
|
1571
|
+
'rem-double/2addr',
|
|
1572
|
+
]);
|
|
1573
|
+
function normalizeOperation(operation) {
|
|
1574
|
+
if (sortRegistersOperations.has(operation.operation)
|
|
1575
|
+
&& 'registers' in operation) {
|
|
1576
|
+
operation.registers.sort((a, b) => a - b);
|
|
1577
|
+
}
|
|
1578
|
+
if (reverseRegistersOperations.has(operation.operation)
|
|
1579
|
+
&& 'registers' in operation) {
|
|
1580
|
+
operation.registers.reverse();
|
|
1581
|
+
}
|
|
1582
|
+
return operation;
|
|
1583
|
+
}
|
|
1584
|
+
export const smaliMethodParser = promiseCompose(createTupleParser([
|
|
523
1585
|
createExactSequenceParser('.method '),
|
|
524
|
-
|
|
525
|
-
|
|
1586
|
+
createOptionalParser(promiseCompose(createTupleParser([
|
|
1587
|
+
smaliAccessFlagsParser,
|
|
1588
|
+
smaliSingleWhitespaceParser,
|
|
1589
|
+
]), ([accessFlags]) => accessFlags)),
|
|
526
1590
|
smaliMemberNameParser,
|
|
527
1591
|
smaliMethodPrototypeParser,
|
|
528
|
-
|
|
1592
|
+
smaliLineEndPraser,
|
|
529
1593
|
smaliExecutableCodeParser,
|
|
530
1594
|
createArrayParser(smaliCodeLineParser),
|
|
531
1595
|
createExactSequenceParser('.end method\n'),
|
|
532
|
-
]), ([_method,
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
const
|
|
540
|
-
|
|
541
|
-
|
|
1596
|
+
]), ([_method, accessFlagsOrUndefined, name, prototype, _newline, { dalvikExecutableCode, parameters, parameterAnnotations, methodAnnotations, }, _lines, _endMethod,]) => {
|
|
1597
|
+
const accessFlags = accessFlagsOrUndefined ?? dalvikExecutableAccessFlagsDefault();
|
|
1598
|
+
let code = dalvikExecutableCode;
|
|
1599
|
+
if (accessFlags.native && code.instructions.length === 0) {
|
|
1600
|
+
code = undefined;
|
|
1601
|
+
}
|
|
1602
|
+
if (code) {
|
|
1603
|
+
const insSize = (accessFlags.static ? 0 : 1) + shortyGetInsSize(prototype.shorty);
|
|
1604
|
+
code.insSize = insSize;
|
|
1605
|
+
for (const operation of code.instructions) {
|
|
1606
|
+
const smaliRegisters = dalvikBytecodeOperationCompanion.getRegisters(operation); // TODO
|
|
1607
|
+
if (smaliRegisters.length === 0) {
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
const registers = smaliRegisters.map(({ prefix, index }) => {
|
|
1611
|
+
if (prefix === 'v') {
|
|
1612
|
+
return index;
|
|
1613
|
+
}
|
|
1614
|
+
if (prefix === 'p') {
|
|
1615
|
+
return code.registersSize - insSize + index;
|
|
1616
|
+
}
|
|
1617
|
+
invariant(false, 'Expected prefix to be v or p');
|
|
1618
|
+
});
|
|
1619
|
+
operation.registers = registers; // TODO
|
|
542
1620
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
1621
|
+
let outsSize = 0;
|
|
1622
|
+
for (const operation of code.instructions) {
|
|
1623
|
+
if (!operation.operation.startsWith('invoke-')) {
|
|
1624
|
+
continue;
|
|
546
1625
|
}
|
|
547
|
-
|
|
548
|
-
|
|
1626
|
+
const registers = dalvikBytecodeOperationCompanion.getRegisters(operation);
|
|
1627
|
+
outsSize = Math.max(outsSize, registers.length); // TODO?: two words for wide types?
|
|
1628
|
+
}
|
|
1629
|
+
code.outsSize = outsSize;
|
|
1630
|
+
// Populate debug info parameter names
|
|
1631
|
+
// The parameter count is insSize minus 1 for non-static methods (to exclude 'this')
|
|
1632
|
+
if (code.debugInfo) {
|
|
1633
|
+
const paramCount = accessFlags.static ? insSize : insSize - 1;
|
|
1634
|
+
const paramNames = Array(paramCount).fill(undefined);
|
|
1635
|
+
// Map parameter names from .param directives
|
|
1636
|
+
// For non-static methods: p0 is 'this', p1 is first param (index 0)
|
|
1637
|
+
// For static methods: p0 is first param (index 0)
|
|
1638
|
+
for (const param of parameters) {
|
|
1639
|
+
if (param.register.prefix === 'p' && param.name) {
|
|
1640
|
+
const pRegister = param.register.index;
|
|
1641
|
+
// Adjust for 'this' parameter in non-static methods
|
|
1642
|
+
const paramIndex = accessFlags.static ? pRegister : pRegister - 1;
|
|
1643
|
+
if (paramIndex >= 0 && paramIndex < paramCount) {
|
|
1644
|
+
paramNames[paramIndex] = param.name;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
549
1647
|
}
|
|
550
|
-
|
|
551
|
-
}
|
|
552
|
-
operation.registers = registers; // TODO
|
|
553
|
-
}
|
|
554
|
-
let outsSize = 0;
|
|
555
|
-
for (const operation of code.instructions) {
|
|
556
|
-
const registers = dalvikBytecodeOperationCompanion.getRegisters(operation);
|
|
557
|
-
outsSize = Math.max(outsSize, registers.length); // TODO: two words for wide types
|
|
1648
|
+
code.debugInfo.parameterNames = paramNames;
|
|
1649
|
+
}
|
|
558
1650
|
}
|
|
559
|
-
code.outsSize = outsSize;
|
|
560
1651
|
return {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
1652
|
+
dalvikExecutableMethodWithAccess: {
|
|
1653
|
+
accessFlags,
|
|
1654
|
+
method: {
|
|
1655
|
+
class: 'FILLED_LATER',
|
|
1656
|
+
prototype,
|
|
1657
|
+
name,
|
|
1658
|
+
},
|
|
1659
|
+
code: code?.instructions.length
|
|
1660
|
+
? {
|
|
1661
|
+
...code,
|
|
1662
|
+
instructions: code.instructions.map(normalizeOperation),
|
|
1663
|
+
}
|
|
1664
|
+
: undefined,
|
|
566
1665
|
},
|
|
567
|
-
|
|
1666
|
+
parameterAnnotations,
|
|
1667
|
+
methodAnnotations,
|
|
568
1668
|
};
|
|
569
1669
|
});
|
|
570
1670
|
setParserName(smaliMethodParser, 'smaliMethodParser');
|
|
571
1671
|
const smaliMethodsParser = promiseCompose(createArrayParser(createDisjunctionParser([
|
|
572
1672
|
smaliMethodParser,
|
|
573
1673
|
smaliCommentsOrNewlinesParser,
|
|
574
|
-
])),
|
|
1674
|
+
])), methodsAndComments => {
|
|
575
1675
|
let type = 'directMethod';
|
|
576
1676
|
const directMethods = [];
|
|
577
1677
|
const virtualMethods = [];
|
|
1678
|
+
const parameterAnnotations = [];
|
|
1679
|
+
const methodAnnotations = [];
|
|
1680
|
+
function pushParameterAnnotation(annotation) {
|
|
1681
|
+
if (annotation.annotations.length === 0) {
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1684
|
+
const existingMethod = parameterAnnotations.find(parameterAnnotation => dalvikExecutableMethodEquals(parameterAnnotation.method, annotation.method));
|
|
1685
|
+
if (existingMethod) {
|
|
1686
|
+
for (const [index, parameterAnnotations_] of annotation.annotations.entries()) {
|
|
1687
|
+
const existingParameterAnnotations = existingMethod.annotations.at(index);
|
|
1688
|
+
if (existingParameterAnnotations) {
|
|
1689
|
+
existingParameterAnnotations.push(...parameterAnnotations_);
|
|
1690
|
+
}
|
|
1691
|
+
else {
|
|
1692
|
+
existingMethod.annotations[index] = parameterAnnotations_;
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
return;
|
|
1696
|
+
}
|
|
1697
|
+
parameterAnnotations.push(annotation);
|
|
1698
|
+
}
|
|
578
1699
|
for (const methodOrComment of methodsAndComments) {
|
|
579
1700
|
if (Array.isArray(methodOrComment)) {
|
|
580
1701
|
for (const comment of methodOrComment) {
|
|
@@ -589,68 +1710,372 @@ const smaliMethodsParser = promiseCompose(createArrayParser(createDisjunctionPar
|
|
|
589
1710
|
}
|
|
590
1711
|
invariant(typeof methodOrComment === 'object', 'Expected method or comment');
|
|
591
1712
|
const method = methodOrComment;
|
|
1713
|
+
if (method.methodAnnotations.length > 0) {
|
|
1714
|
+
methodAnnotations.push({
|
|
1715
|
+
method: method.dalvikExecutableMethodWithAccess.method,
|
|
1716
|
+
annotations: method.methodAnnotations.map(annotation => ({
|
|
1717
|
+
type: annotation.type,
|
|
1718
|
+
visibility: annotation.visibility,
|
|
1719
|
+
elements: annotation.elements.map(element => ({
|
|
1720
|
+
name: element.name,
|
|
1721
|
+
value: convertToTaggedEncodedValue(element.value),
|
|
1722
|
+
})),
|
|
1723
|
+
})),
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
// Create an annotations array for all parameters, not just those with annotations
|
|
1727
|
+
// In smali, instance methods have p0 as 'this', p1 as first param, etc.
|
|
1728
|
+
// But DEX parameter annotations only include the actual parameters (not 'this')
|
|
1729
|
+
const isStatic = method.dalvikExecutableMethodWithAccess.accessFlags.static;
|
|
1730
|
+
const smaliRegisterOffset = isStatic ? 0 : 1; // P0 is 'this' for instance methods
|
|
1731
|
+
const allParameterAnnotations = method.dalvikExecutableMethodWithAccess.method.prototype.parameters.map((_, parameterIndex) => {
|
|
1732
|
+
const smaliRegisterIndex = parameterIndex + smaliRegisterOffset;
|
|
1733
|
+
const parameterAnnotation = method.parameterAnnotations.find(pa => pa.register.prefix === 'p' && pa.register.index === smaliRegisterIndex);
|
|
1734
|
+
if (parameterAnnotation?.annotation) {
|
|
1735
|
+
return [{
|
|
1736
|
+
type: parameterAnnotation.annotation.type,
|
|
1737
|
+
visibility: parameterAnnotation.annotation.visibility,
|
|
1738
|
+
elements: parameterAnnotation.annotation.elements.map(element => ({
|
|
1739
|
+
name: element.name,
|
|
1740
|
+
value: convertToTaggedEncodedValue(element.value),
|
|
1741
|
+
})),
|
|
1742
|
+
}];
|
|
1743
|
+
}
|
|
1744
|
+
return [];
|
|
1745
|
+
});
|
|
1746
|
+
// Only push parameter annotations if there are some actual annotations
|
|
1747
|
+
if (allParameterAnnotations.some(annotations => annotations.length > 0)) {
|
|
1748
|
+
pushParameterAnnotation({
|
|
1749
|
+
method: method.dalvikExecutableMethodWithAccess.method,
|
|
1750
|
+
annotations: allParameterAnnotations,
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
592
1753
|
if (type === 'directMethod') {
|
|
593
|
-
directMethods.push(method);
|
|
1754
|
+
directMethods.push(method.dalvikExecutableMethodWithAccess);
|
|
594
1755
|
continue;
|
|
595
1756
|
}
|
|
596
1757
|
if (type === 'virtualMethod') {
|
|
597
|
-
virtualMethods.push(method);
|
|
1758
|
+
virtualMethods.push(method.dalvikExecutableMethodWithAccess);
|
|
598
1759
|
continue;
|
|
599
1760
|
}
|
|
600
1761
|
invariant(false, 'Expected method type');
|
|
601
1762
|
}
|
|
1763
|
+
// Sort parameter annotations by method index in the combined method list
|
|
1764
|
+
// to match the order in the DEX file's annotations directory
|
|
1765
|
+
const allMethods = [...directMethods, ...virtualMethods];
|
|
1766
|
+
parameterAnnotations.sort((a, b) => {
|
|
1767
|
+
const indexA = allMethods.findIndex(m => dalvikExecutableMethodEquals(m.method, a.method));
|
|
1768
|
+
const indexB = allMethods.findIndex(m => dalvikExecutableMethodEquals(m.method, b.method));
|
|
1769
|
+
return indexA - indexB;
|
|
1770
|
+
});
|
|
602
1771
|
return {
|
|
603
1772
|
directMethods,
|
|
604
1773
|
virtualMethods,
|
|
1774
|
+
parameterAnnotations,
|
|
1775
|
+
methodAnnotations,
|
|
605
1776
|
};
|
|
606
1777
|
});
|
|
607
1778
|
setParserName(smaliMethodsParser, 'smaliMethodsParser');
|
|
608
1779
|
export const smaliParser = promiseCompose(createTupleParser([
|
|
609
1780
|
smaliClassDeclarationParser,
|
|
610
1781
|
smaliSuperDeclarationParser,
|
|
611
|
-
smaliSourceDeclarationParser,
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
1782
|
+
createOptionalParser(smaliSourceDeclarationParser),
|
|
1783
|
+
createOptionalParser(promiseCompose(createTupleParser([
|
|
1784
|
+
smaliCommentsOrNewlinesParser,
|
|
1785
|
+
createNonEmptyArrayParser(smaliInterfaceDeclarationParser),
|
|
1786
|
+
]), ([_commentsOrNewlines, interfaces,]) => interfaces)),
|
|
1787
|
+
createOptionalParser(promiseCompose(createTupleParser([
|
|
1788
|
+
smaliCommentsOrNewlinesParser,
|
|
1789
|
+
createSeparatedNonEmptyArrayParser(smaliAnnotationParser, smaliCommentsOrNewlinesParser),
|
|
1790
|
+
]), ([_commentsOrNewlines, classAnnotations,]) => classAnnotations)),
|
|
1791
|
+
createOptionalParser(promiseCompose(createTupleParser([
|
|
1792
|
+
smaliCommentsOrNewlinesParser,
|
|
1793
|
+
smaliFieldsParser,
|
|
1794
|
+
]), ([_commentsOrNewlines, fields,]) => fields)),
|
|
616
1795
|
smaliMethodsParser,
|
|
617
|
-
]), ([{ accessFlags, class: class_, }, { superclass, },
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
1796
|
+
]), ([{ accessFlags, class: class_, }, { superclass, }, sourceFileObject, interfaces, classAnnotations, smaliFields, methods,]) => {
|
|
1797
|
+
const sourceFile = sourceFileObject?.sourceFile;
|
|
1798
|
+
// Create staticValues array matching DEX format:
|
|
1799
|
+
// - Find the last static field with a non-default initializer
|
|
1800
|
+
// - Create array up to that index with values/nulls
|
|
1801
|
+
// - Fields after the last initializer are not included
|
|
1802
|
+
// - Default values (false, null) are not considered initializers
|
|
1803
|
+
const staticFieldsList = smaliFields?.staticFields ?? [];
|
|
1804
|
+
let lastIndexWithInitializer = -1;
|
|
1805
|
+
for (let i = staticFieldsList.length - 1; i >= 0; i--) {
|
|
1806
|
+
const initValue = staticFieldsList[i].initialValue;
|
|
1807
|
+
// Only consider non-default values as initializers
|
|
1808
|
+
// Numbers, bigints, strings, and true are non-default
|
|
1809
|
+
// false and null are default values
|
|
1810
|
+
if (initValue !== undefined && typeof initValue === 'number') {
|
|
1811
|
+
lastIndexWithInitializer = i;
|
|
1812
|
+
break;
|
|
1813
|
+
}
|
|
1814
|
+
if (initValue !== undefined && typeof initValue === 'bigint') {
|
|
1815
|
+
lastIndexWithInitializer = i;
|
|
1816
|
+
break;
|
|
1817
|
+
}
|
|
1818
|
+
if (initValue !== undefined && typeof initValue === 'string') {
|
|
1819
|
+
lastIndexWithInitializer = i;
|
|
1820
|
+
break;
|
|
1821
|
+
}
|
|
1822
|
+
if (initValue === true) {
|
|
1823
|
+
lastIndexWithInitializer = i;
|
|
1824
|
+
break;
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
const staticValues = lastIndexWithInitializer === -1
|
|
1828
|
+
? []
|
|
1829
|
+
: staticFieldsList
|
|
1830
|
+
.slice(0, lastIndexWithInitializer + 1)
|
|
1831
|
+
.map(smaliField => {
|
|
1832
|
+
const fieldType = smaliField.field.field.type;
|
|
1833
|
+
if (smaliField.initialValue === undefined) {
|
|
1834
|
+
// For integer types without initializer, DEX stores 0
|
|
1835
|
+
if (fieldType === 'I') {
|
|
1836
|
+
return { type: 'int', value: 0 };
|
|
1837
|
+
}
|
|
1838
|
+
if (fieldType === 'B') {
|
|
1839
|
+
return { type: 'byte', value: 0 };
|
|
1840
|
+
}
|
|
1841
|
+
if (fieldType === 'S') {
|
|
1842
|
+
return { type: 'short', value: 0 };
|
|
1843
|
+
}
|
|
1844
|
+
if (fieldType === 'C') {
|
|
1845
|
+
return { type: 'char', value: 0 };
|
|
1846
|
+
}
|
|
1847
|
+
// For long types without initializer, DEX stores 0n
|
|
1848
|
+
if (fieldType === 'J') {
|
|
1849
|
+
return { type: 'long', value: 0n };
|
|
1850
|
+
}
|
|
1851
|
+
// For float/double types without initializer, DEX stores 0
|
|
1852
|
+
if (fieldType === 'F') {
|
|
1853
|
+
return { type: 'float', value: 0 };
|
|
1854
|
+
}
|
|
1855
|
+
if (fieldType === 'D') {
|
|
1856
|
+
return { type: 'double', value: 0 };
|
|
1857
|
+
}
|
|
1858
|
+
// For boolean types without initializer, DEX stores false
|
|
1859
|
+
if (fieldType === 'Z') {
|
|
1860
|
+
return { type: 'boolean', value: false };
|
|
1861
|
+
}
|
|
1862
|
+
// For other types (reference types, etc.), return null
|
|
1863
|
+
return { type: 'null', value: null };
|
|
1864
|
+
}
|
|
1865
|
+
// Numeric values are stored in static values array
|
|
1866
|
+
if (typeof smaliField.initialValue === 'number') {
|
|
1867
|
+
// Convert to BigInt for long (J) types
|
|
1868
|
+
if (fieldType === 'J') {
|
|
1869
|
+
return { type: 'long', value: BigInt(smaliField.initialValue) };
|
|
1870
|
+
}
|
|
1871
|
+
if (fieldType === 'B') {
|
|
1872
|
+
return { type: 'byte', value: smaliField.initialValue };
|
|
1873
|
+
}
|
|
1874
|
+
if (fieldType === 'S') {
|
|
1875
|
+
return { type: 'short', value: smaliField.initialValue };
|
|
1876
|
+
}
|
|
1877
|
+
if (fieldType === 'C') {
|
|
1878
|
+
return { type: 'char', value: smaliField.initialValue };
|
|
1879
|
+
}
|
|
1880
|
+
if (fieldType === 'F') {
|
|
1881
|
+
return { type: 'float', value: smaliField.initialValue };
|
|
1882
|
+
}
|
|
1883
|
+
if (fieldType === 'D') {
|
|
1884
|
+
return { type: 'double', value: smaliField.initialValue };
|
|
1885
|
+
}
|
|
1886
|
+
// Default to int for other numeric types
|
|
1887
|
+
return { type: 'int', value: smaliField.initialValue };
|
|
1888
|
+
}
|
|
1889
|
+
// BigInt values for long (J) types
|
|
1890
|
+
if (typeof smaliField.initialValue === 'bigint') {
|
|
1891
|
+
return { type: 'long', value: smaliField.initialValue };
|
|
1892
|
+
}
|
|
1893
|
+
// String values should be stored as string type
|
|
1894
|
+
if (typeof smaliField.initialValue === 'string') {
|
|
1895
|
+
return { type: 'string', value: smaliField.initialValue };
|
|
1896
|
+
}
|
|
1897
|
+
// Boolean true is a non-default value and should be in staticValues
|
|
1898
|
+
if (smaliField.initialValue === true) {
|
|
1899
|
+
return { type: 'boolean', value: true };
|
|
1900
|
+
}
|
|
1901
|
+
// Boolean false and null are default values
|
|
1902
|
+
// For boolean fields with explicit false, return false
|
|
1903
|
+
if (smaliField.initialValue === false && fieldType === 'Z') {
|
|
1904
|
+
return { type: 'boolean', value: false };
|
|
1905
|
+
}
|
|
1906
|
+
// For null or other default values, return null
|
|
1907
|
+
return { type: 'null', value: null };
|
|
1908
|
+
});
|
|
1909
|
+
const fields = {
|
|
1910
|
+
staticFields: smaliFields?.staticFields.map(({ field }) => field) ?? [],
|
|
1911
|
+
instanceFields: smaliFields?.instanceFields.map(({ field }) => field) ?? [],
|
|
1912
|
+
};
|
|
1913
|
+
// Sort fields to match DEX file order (by field name, then type)
|
|
1914
|
+
// This matches the field order in class_data_item in DEX files
|
|
1915
|
+
// Use binary string comparison (case-sensitive) to match UTF-8 byte order
|
|
1916
|
+
const sortFields = (fieldsList) => {
|
|
1917
|
+
fieldsList.sort((a, b) => {
|
|
1918
|
+
// First by field name (case-sensitive comparison)
|
|
1919
|
+
if (a.field.name !== b.field.name) {
|
|
1920
|
+
return a.field.name < b.field.name ? -1 : 1;
|
|
1921
|
+
}
|
|
1922
|
+
// Then by field type (case-sensitive comparison)
|
|
1923
|
+
return a.field.type < b.field.type ? -1 : 1;
|
|
1924
|
+
});
|
|
1925
|
+
};
|
|
1926
|
+
sortFields(fields.staticFields);
|
|
1927
|
+
sortFields(fields.instanceFields);
|
|
1928
|
+
const annotations = {
|
|
1929
|
+
classAnnotations: (classAnnotations ?? []).map(annotation => ({
|
|
1930
|
+
type: annotation.type,
|
|
1931
|
+
visibility: annotation.visibility,
|
|
1932
|
+
elements: annotation.elements.map(element => ({
|
|
1933
|
+
name: element.name,
|
|
1934
|
+
value: convertToTaggedEncodedValue(element.value),
|
|
1935
|
+
})),
|
|
651
1936
|
})),
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
}
|
|
1937
|
+
fieldAnnotations: [],
|
|
1938
|
+
methodAnnotations: methods.methodAnnotations,
|
|
1939
|
+
parameterAnnotations: methods.parameterAnnotations,
|
|
1940
|
+
};
|
|
1941
|
+
for (const smaliField of [...(smaliFields?.staticFields ?? []), ...(smaliFields?.instanceFields ?? [])]) {
|
|
1942
|
+
if (smaliField.annotations.length === 0) {
|
|
1943
|
+
continue;
|
|
1944
|
+
}
|
|
1945
|
+
const existingFieldAnnotations = annotations.fieldAnnotations.find(fieldAnnotation => dalvikExecutableFieldEquals(fieldAnnotation.field, smaliField.field.field));
|
|
1946
|
+
if (existingFieldAnnotations) {
|
|
1947
|
+
existingFieldAnnotations.annotations ??= [];
|
|
1948
|
+
existingFieldAnnotations.annotations.push(...smaliField.annotations.map(annotation => ({
|
|
1949
|
+
type: annotation.type,
|
|
1950
|
+
visibility: annotation.visibility,
|
|
1951
|
+
elements: annotation.elements.map(element => ({
|
|
1952
|
+
name: element.name,
|
|
1953
|
+
value: convertToTaggedEncodedValue(element.value),
|
|
1954
|
+
})),
|
|
1955
|
+
})));
|
|
1956
|
+
continue;
|
|
1957
|
+
}
|
|
1958
|
+
annotations.fieldAnnotations.push({
|
|
1959
|
+
field: smaliField.field.field,
|
|
1960
|
+
annotations: smaliField.annotations.map(annotation => ({
|
|
1961
|
+
type: annotation.type,
|
|
1962
|
+
visibility: annotation.visibility,
|
|
1963
|
+
elements: annotation.elements.map(element => ({
|
|
1964
|
+
name: element.name,
|
|
1965
|
+
value: convertToTaggedEncodedValue(element.value),
|
|
1966
|
+
})),
|
|
1967
|
+
})),
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
// Sort field annotations to match DEX file order (by field name, then type)
|
|
1971
|
+
// This matches the annotations_directory_item field_annotations order in DEX files
|
|
1972
|
+
// Use binary string comparison (case-sensitive) to match UTF-8 byte order
|
|
1973
|
+
annotations.fieldAnnotations.sort((a, b) => {
|
|
1974
|
+
// First by field name (case-sensitive comparison)
|
|
1975
|
+
if (a.field.name !== b.field.name) {
|
|
1976
|
+
return a.field.name < b.field.name ? -1 : 1;
|
|
1977
|
+
}
|
|
1978
|
+
// Then by field type (case-sensitive comparison)
|
|
1979
|
+
return a.field.type < b.field.type ? -1 : 1;
|
|
1980
|
+
});
|
|
1981
|
+
// Compute synthetic flag from members if not explicitly set
|
|
1982
|
+
// This matches baksmali behavior where synthetic flag at class level is not output
|
|
1983
|
+
// but can be inferred from all members being synthetic
|
|
1984
|
+
const allMembers = [
|
|
1985
|
+
...fields.staticFields,
|
|
1986
|
+
...fields.instanceFields,
|
|
1987
|
+
...methods.directMethods,
|
|
1988
|
+
// Note: virtualMethods are not included, matching DEX parser behavior
|
|
1989
|
+
];
|
|
1990
|
+
const allMembersAreSynthetic = (allMembers.every(member => member.accessFlags.synthetic)
|
|
1991
|
+
&& allMembers.length > 0);
|
|
1992
|
+
const finalAccessFlags = {
|
|
1993
|
+
...accessFlags,
|
|
1994
|
+
// Use the synthetic flag from the class declaration, or compute it from members if not set
|
|
1995
|
+
synthetic: accessFlags.synthetic || allMembersAreSynthetic,
|
|
1996
|
+
};
|
|
1997
|
+
return {
|
|
1998
|
+
accessFlags: finalAccessFlags,
|
|
1999
|
+
class: class_,
|
|
2000
|
+
superclass,
|
|
2001
|
+
sourceFile,
|
|
2002
|
+
annotations: ((annotations.classAnnotations?.length
|
|
2003
|
+
|| annotations.fieldAnnotations?.length
|
|
2004
|
+
|| annotations.methodAnnotations?.length
|
|
2005
|
+
|| annotations.parameterAnnotations?.length)
|
|
2006
|
+
? ({
|
|
2007
|
+
classAnnotations: annotations.classAnnotations,
|
|
2008
|
+
fieldAnnotations: annotations.fieldAnnotations.map(fieldAnnotation => ({
|
|
2009
|
+
...fieldAnnotation,
|
|
2010
|
+
field: {
|
|
2011
|
+
...fieldAnnotation.field,
|
|
2012
|
+
class: class_,
|
|
2013
|
+
},
|
|
2014
|
+
})),
|
|
2015
|
+
methodAnnotations: annotations.methodAnnotations
|
|
2016
|
+
.map(methodAnnotation => ({
|
|
2017
|
+
...methodAnnotation,
|
|
2018
|
+
method: {
|
|
2019
|
+
...methodAnnotation.method,
|
|
2020
|
+
class: class_,
|
|
2021
|
+
},
|
|
2022
|
+
}))
|
|
2023
|
+
// Sort method annotations to match DEX file order (lexicographic by method name, then prototype shorty)
|
|
2024
|
+
// This matches the method_idx order in the DEX file's method_id table
|
|
2025
|
+
.sort((a, b) => {
|
|
2026
|
+
// Sort by method name first (using code point comparison, not locale-aware)
|
|
2027
|
+
if (a.method.name !== b.method.name) {
|
|
2028
|
+
return a.method.name < b.method.name ? -1 : 1;
|
|
2029
|
+
}
|
|
2030
|
+
// Then by shorty (prototype signature)
|
|
2031
|
+
return a.method.prototype.shorty < b.method.prototype.shorty ? -1 : 1;
|
|
2032
|
+
}),
|
|
2033
|
+
parameterAnnotations: annotations.parameterAnnotations.map(parameterAnnotation => ({
|
|
2034
|
+
...parameterAnnotation,
|
|
2035
|
+
method: {
|
|
2036
|
+
...parameterAnnotation.method,
|
|
2037
|
+
class: class_,
|
|
2038
|
+
},
|
|
2039
|
+
})),
|
|
2040
|
+
})
|
|
2041
|
+
: undefined),
|
|
2042
|
+
classData: ((methods.directMethods.length > 0
|
|
2043
|
+
|| methods.virtualMethods.length > 0
|
|
2044
|
+
|| (fields?.staticFields.length ?? 0)
|
|
2045
|
+
|| (fields?.instanceFields.length ?? 0))
|
|
2046
|
+
? ({
|
|
2047
|
+
directMethods: methods.directMethods.map(method => ({
|
|
2048
|
+
...method,
|
|
2049
|
+
method: {
|
|
2050
|
+
...method.method,
|
|
2051
|
+
class: class_,
|
|
2052
|
+
},
|
|
2053
|
+
})),
|
|
2054
|
+
virtualMethods: methods.virtualMethods.map(method => ({
|
|
2055
|
+
...method,
|
|
2056
|
+
method: {
|
|
2057
|
+
...method.method,
|
|
2058
|
+
class: class_,
|
|
2059
|
+
},
|
|
2060
|
+
})),
|
|
2061
|
+
staticFields: (fields?.staticFields ?? []).map(field => ({
|
|
2062
|
+
...field,
|
|
2063
|
+
field: {
|
|
2064
|
+
...field.field,
|
|
2065
|
+
class: class_,
|
|
2066
|
+
},
|
|
2067
|
+
})),
|
|
2068
|
+
instanceFields: (fields?.instanceFields ?? []).map(field => ({
|
|
2069
|
+
...field,
|
|
2070
|
+
field: {
|
|
2071
|
+
...field.field,
|
|
2072
|
+
class: class_,
|
|
2073
|
+
},
|
|
2074
|
+
})),
|
|
2075
|
+
})
|
|
2076
|
+
: undefined),
|
|
2077
|
+
interfaces: interfaces ?? [],
|
|
2078
|
+
staticValues,
|
|
2079
|
+
};
|
|
2080
|
+
});
|
|
656
2081
|
setParserName(smaliParser, 'smaliParser');
|