Files
compiler-explorer/test/android-tests.ts
Matt Godbolt 2ae38a0c3f Make argument parsers instances instead of static classes (#8017)
- Made parsers stateful instances instead of shared static state (for
mllvm options). Fixes #8011 as this is caused by multiple clang-based
compilers being run concurrently and stomping over each others' state.
- passes `Compiler` to the constructor, which removes some param passing
- Added some missing awaits
- Tried to get things less dependent on `examples`, only `go` needs it
- Spotted that `zig` c++ might have issues in discovery
- Fly-by fixed a broken go path in ppc64le_gl122
- removed a redundant override in coccinelle
- made the mojo parser actually use the parser it defined
- canonified tablegen's special method
- 

I changed the zig parser too but as best I can tell it was broken before
(the `1` return value from the command it runs:)

```
ubuntu@ip-172-30-0-164:/infra/.deploy$ /opt/compiler-explorer/zig-0.14.1/zig c++ -mllvm --help-list-hidden /infra/.deploy/examples/c++/default.cpp -S -o /tmp/output.s
...
  --x86-use-vzeroupper                                                       - Minimize AVX to SSE transition penalty
  --xcore-max-threads=<number>                                               - Maximum number of threads (for emulation thread-local storage)
/infra/.deploy/examples/c++/default.cpp:1:1: error: FileNotFound
```
return code 1 (means it's not cached)

---------

Co-authored-by: Partouf <partouf@gmail.com>
2025-08-11 12:14:13 -05:00

328 lines
13 KiB
TypeScript

// Copyright (c) 2024, Compiler Explorer Authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
import fs from 'node:fs';
import {beforeAll, describe, expect, it} from 'vitest';
import {CompilationEnvironment} from '../lib/compilation-env.js';
import {Dex2OatCompiler} from '../lib/compilers/index.js';
import * as utils from '../lib/utils.js';
import {ParsedAsmResultLine} from '../types/asmresult/asmresult.interfaces.js';
import {CompilerInfo} from '../types/compiler.interfaces.js';
import {makeCompilationEnvironment} from './utils.js';
const languages = {
androidJava: {id: 'android-java'},
androidKotlin: {id: 'android-kotlin'},
};
const androidJavaInfo = {
exe: 'java',
remote: true,
lang: languages.androidJava.id,
} as unknown as CompilerInfo;
const androidKotlinInfo = {
exe: 'kotlin',
remote: true,
lang: languages.androidKotlin.id,
} as unknown as CompilerInfo;
describe('dex2oat', () => {
let env: CompilationEnvironment;
beforeAll(() => {
env = makeCompilationEnvironment({languages});
});
describe('android-java', () => {
it('Should not crash on instantiation', () => {
new Dex2OatCompiler(androidJavaInfo, env);
});
it('Output is shown as-is if full output mode is enabled', () => {
return testParseOatdump(androidJavaInfo, 'test/android/java', true);
});
if (process.platform !== 'win32') {
it('Output is parsed and formatted if full output mode is disabled', () => {
return testParseOatdump(androidJavaInfo, 'test/android/java', false);
});
}
it('Line numbers are correctly extracted from .smali files', () => {
return testParseSmaliForLineNumbers(androidJavaInfo, 'test/android/parse-data');
});
it("Classes with 'L' in the name compile correctly", () => {
return testParseSmaliClassWithL(androidJavaInfo, 'test/android/parse-data');
});
it('Inner classes are correctly read in .smali files', () => {
return testParseSmaliInnerClasses(androidJavaInfo, 'test/android/parse-data');
});
it('Dex PCs are correctly extracted from classes.cfg', () => {
return testParsePassDumpsForDexPcs(androidJavaInfo, 'test/android/parse-data');
});
it('Internal primitive descriptors are correctly translated to full names', () => {
return testPrettyDescriptorsPrimitive(androidJavaInfo);
});
it('Internal reference descriptors are correctly prettified', () => {
return testPrettyDescriptorsReference(androidJavaInfo);
});
it('Internal descriptors with dimensions are correctly prettified', () => {
return testPrettyDescriptorsDimensions(androidJavaInfo);
});
it('Method parameters are correctly split into component parts', () => {
return testSplitMethodParameters(androidJavaInfo);
});
it('Method signature is prettified correctly', () => {
return testPrettyMethodSignature(androidJavaInfo);
});
});
describe('android-kotlin', () => {
it('Should not crash on instantiation', () => {
new Dex2OatCompiler(androidKotlinInfo, env);
});
it('Output is shown as-is if full output mode is enabled', () => {
return testParseOatdump(androidKotlinInfo, 'test/android/kotlin', true);
});
if (process.platform !== 'win32') {
it('Output is parsed and formatted if full output mode is disabled', () => {
return testParseOatdump(androidKotlinInfo, 'test/android/kotlin', false);
});
}
it('Line numbers are correctly extracted from .smali files', () => {
return testParseSmaliForLineNumbers(androidKotlinInfo, 'test/android/parse-data');
});
it("Classes with 'L' in the name compile correctly", () => {
return testParseSmaliClassWithL(androidKotlinInfo, 'test/android/parse-data');
});
it('Inner classes are correctly read in .smali files', () => {
return testParseSmaliInnerClasses(androidKotlinInfo, 'test/android/parse-data');
});
it('Dex PCs are correctly extracted from classes.cfg', () => {
return testParsePassDumpsForDexPcs(androidKotlinInfo, 'test/android/parse-data');
});
it('Internal primitive descriptors are correctly translated to full names', () => {
return testPrettyDescriptorsPrimitive(androidKotlinInfo);
});
it('Internal reference descriptors are correctly prettified', () => {
return testPrettyDescriptorsReference(androidKotlinInfo);
});
it('Internal descriptors with dimensions are correctly prettified', () => {
return testPrettyDescriptorsDimensions(androidKotlinInfo);
});
it('Method parameters are correctly split into component parts', () => {
return testSplitMethodParameters(androidKotlinInfo);
});
it('Method signature is prettified correctly', () => {
return testPrettyMethodSignature(androidKotlinInfo);
});
});
async function testParseOatdump(info: CompilerInfo, baseFolder: string, fullOutput: boolean) {
const compiler = new Dex2OatCompiler(info, env);
compiler.fullOutput = fullOutput;
// The "result" of running oatdump.
const asm = [{text: fs.readFileSync(`${baseFolder}/oatdump.asm`, 'utf-8')}];
const objdumpResult = {
asm,
};
const processed = await compiler.processAsm(objdumpResult, compiler.getDefaultFilters());
expect(processed).toHaveProperty('asm');
const actualSegments = (processed as {asm: ParsedAsmResultLine[]}).asm;
// fullOutput results in no processing, with the entire oatdump text
// being returned as one long string.
const output = fullOutput
? [fs.readFileSync(`${baseFolder}/oatdump.asm`, 'utf-8')]
: utils.splitLines(fs.readFileSync(`${baseFolder}/output.asm`, 'utf-8'));
const expectedSegments = output.map(line => {
return {
text: line,
source: null,
};
});
expect(actualSegments).toEqual(expectedSegments);
}
async function testParseSmaliForLineNumbers(info: CompilerInfo, baseFolder: string) {
const compiler = new Dex2OatCompiler(info, env);
const rawSmaliText = fs.readFileSync(`${baseFolder}/Square.smali`, {encoding: 'utf8'});
const dexPcsToLines: Record<string, Record<number, number>> = {};
compiler.parseSmaliForLineNumbers(dexPcsToLines, rawSmaliText.split(/\n/));
expect(Object.keys(dexPcsToLines)).toHaveLength(2);
expect(dexPcsToLines).toHaveProperty('void Square.<init>()', {0: 12, 3: 12});
expect(dexPcsToLines).toHaveProperty('int Square.square(int)', {0: 14, 1: 14});
}
async function testParseSmaliClassWithL(info: CompilerInfo, baseFolder: string) {
const compiler = new Dex2OatCompiler(info, env);
const rawSmaliText = fs.readFileSync(`${baseFolder}/ClassWithL.smali`, {encoding: 'utf8'});
const dexPcsToLines: Record<string, Record<number, number>> = {};
compiler.parseSmaliForLineNumbers(dexPcsToLines, rawSmaliText.split(/\n/));
expect(Object.keys(dexPcsToLines)).toHaveLength(2);
expect(dexPcsToLines).toHaveProperty('void LSLqLuLaLrLeL.<init>()', {0: 12, 3: 12});
expect(dexPcsToLines).toHaveProperty('int LSLqLuLaLrLeL.square(int)', {0: 14, 1: 14});
}
async function testParseSmaliInnerClasses(info: CompilerInfo, baseFolder: string) {
const compiler = new Dex2OatCompiler(info, env);
const rawSmaliText = fs.readFileSync(`${baseFolder}/InnerClassCases.smali`, {encoding: 'utf8'});
const dexPcsToLines: Record<string, Record<number, number>> = {};
compiler.parseSmaliForLineNumbers(dexPcsToLines, rawSmaliText.split(/\n/));
expect(Object.keys(dexPcsToLines)).toHaveLength(6);
// Self
expect(dexPcsToLines).toHaveProperty('void InnerClassCases.<init>()', {0: 1, 3: 1});
// Non-static
expect(dexPcsToLines).toHaveProperty('void InnerClassCases$InnerClass.<init>(InnerClassCases)', {
0: 2,
2: 2,
5: 2,
});
expect(dexPcsToLines).toHaveProperty('void InnerClassCases$FinalInnerClass.<init>(InnerClassCases)', {
0: 3,
2: 3,
5: 3,
});
// Static
expect(dexPcsToLines).toHaveProperty('void InnerClassCases$StaticInnerClass.<init>()', {0: 4, 3: 4});
expect(dexPcsToLines).toHaveProperty('void InnerClassCases$StaticFinalInnerClass.<init>()', {0: 5, 3: 5});
expect(dexPcsToLines).toHaveProperty('void InnerClassCases$LStartsWithL.<init>()', {0: 6, 3: 6});
}
async function testParsePassDumpsForDexPcs(info: CompilerInfo, baseFolder: string) {
const compiler = new Dex2OatCompiler(info, env);
const rawCfgText = fs.readFileSync(`${baseFolder}/classes.cfg`, {encoding: 'utf8'});
const methodsAndOffsetsToDexPcs = compiler.passDumpParser.parsePassDumpsForDexPcs(rawCfgText.split(/\n/));
expect(Object.keys(methodsAndOffsetsToDexPcs)).toHaveLength(2);
expect(methodsAndOffsetsToDexPcs).toHaveProperty('void Square.<init>()', {0: 3});
expect(methodsAndOffsetsToDexPcs).toHaveProperty('int Square.square(int)', {0: 0, 4: 1});
}
async function testPrettyDescriptorsPrimitive(info: CompilerInfo) {
const compiler = new Dex2OatCompiler(info, env);
const primitiveMap = {
V: 'void',
Z: 'boolean',
B: 'byte',
S: 'short',
C: 'char',
I: 'int',
J: 'long',
F: 'float',
D: 'double',
};
for (const input in primitiveMap) {
expect(compiler.prettyDescriptor(input)).toEqual(primitiveMap[input]);
}
}
async function testPrettyDescriptorsReference(info: CompilerInfo) {
const compiler = new Dex2OatCompiler(info, env);
const referenceMap = {
'Ljava/lang/String;': 'java.lang.String',
'Landroid/util/Log;': 'android.util.Log',
'Landroidx/annotation/DoNotInline;': 'androidx.annotation.DoNotInline',
};
for (const input in referenceMap) {
expect(compiler.prettyDescriptor(input)).toEqual(referenceMap[input]);
}
}
async function testPrettyDescriptorsDimensions(info: CompilerInfo) {
const compiler = new Dex2OatCompiler(info, env);
const dimensionsMap = {
'[I': 'int[]',
'[[Z': 'boolean[][]',
'[[[Ljava/lang/Integer;': 'java.lang.Integer[][][]',
'[[[[Landroid/graphics/Bitmap;': 'android.graphics.Bitmap[][][][]',
};
for (const input in dimensionsMap) {
expect(compiler.prettyDescriptor(input)).toEqual(dimensionsMap[input]);
}
}
async function testSplitMethodParameters(info: CompilerInfo) {
const compiler = new Dex2OatCompiler(info, env);
const parametersMap = {
I: ['I'],
II: ['I', 'I'],
IIZ: ['I', 'I', 'Z'],
'Ljava/lang/String;': ['Ljava/lang/String;'],
'IILjava/lang/String;': ['I', 'I', 'Ljava/lang/String;'],
'[JJ[Ljava/lang/String;Z': ['[J', 'J', '[Ljava/lang/String;', 'Z'],
};
for (const input in parametersMap) {
expect(compiler.splitMethodParameters(input)).toEqual(parametersMap[input]);
}
}
async function testPrettyMethodSignature(info: CompilerInfo) {
const compiler = new Dex2OatCompiler(info, env);
const methodMap = {
'square(I)I': 'int square(int)',
'stringAppend(Ljava/lang/String;)Ljava/lang/String;': 'java.lang.String stringAppend(java.lang.String)',
'push([II)[I': 'int[] push(int[], int)',
};
for (const input in methodMap) {
expect(compiler.prettyMethodSignature(input)).toEqual(methodMap[input]);
}
}
});