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