Repository: compiler-explorer/compiler-explorer
Pull request #7460 (2025-02-26 19:51:12)

Improvements to r8/d8 dex stuff
- better handling of missing files (doesn't crash)
- better error reporting.


Patch:
diff --git a/lib/compilers/d8.ts b/lib/compilers/d8.ts
index 8c47d6c67..350f71e1b 100644
--- a/lib/compilers/d8.ts
+++ b/lib/compilers/d8.ts
@@ -103,9 +103,18 @@ export class D8Compiler extends BaseCompiler implements SimpleOutputFilenameComp
         let outputFilename = '';
         let initialResult: CompilationResult | null = null;
 
-        const javaCompiler = unwrap(
-            global.handler_config.compileHandler.findCompiler('java', this.javaId),
-        ) as JavaCompiler;
+        const javaCompiler = global.handler_config.compileHandler.findCompiler('java', this.javaId) as
+            | JavaCompiler
+            | undefined;
+        if (!javaCompiler) {
+            return {
+                ...this.handleUserError(
+                    {message: `Compiler ${this.lang.id} ${this.javaId} not configured correctly`},
+                    '',
+                ),
+                timedOut: false,
+            };
+        }
 
         // Instantiate Java or Kotlin compiler based on the current language.
         if (this.lang.id === 'android-java') {
@@ -216,10 +225,13 @@ export class D8Compiler extends BaseCompiler implements SimpleOutputFilenameComp
         let files = await fs.readdir(dirPath);
         const dexFile = files.find(f => f.endsWith('.dex'));
         const baksmaliOptions = ['-jar', this.compiler.objdumper, 'd', `${dexFile}`, '--code-offsets', '-o', dirPath];
-        await this.exec(javaCompiler.javaRuntime, baksmaliOptions, {
+        const execResult = await this.exec(javaCompiler.javaRuntime, baksmaliOptions, {
             maxOutput: maxSize,
             customCwd: dirPath,
         });
+        if (execResult.code !== 0) {
+            logger.warn(`baksmali failed: ${execResult.stderr}\n${execResult.stdout}`);
+        }
 
         // There is one smali file for each class.
         files = await fs.readdir(dirPath);
diff --git a/lib/compilers/dex2oat.ts b/lib/compilers/dex2oat.ts
index d8bb27a8c..eb30347ad 100644
--- a/lib/compilers/dex2oat.ts
+++ b/lib/compilers/dex2oat.ts
@@ -37,10 +37,8 @@ import type {PreliminaryCompilerInfo} from '../../types/compiler.interfaces.js';
 import type {UnprocessedExecResult} from '../../types/execution/execution.interfaces.js';
 import type {ParseFiltersAndOutputOptions} from '../../types/features/filters.interfaces.js';
 import type {SelectedLibraryVersion} from '../../types/libraries/libraries.interfaces.js';
-import {unwrap} from '../assert.js';
 import {BaseCompiler} from '../base-compiler.js';
 import {CompilationEnvironment} from '../compilation-env.js';
-import {logger} from '../logger.js';
 import {Dex2OatPassDumpParser} from '../parsers/dex2oat-pass-dump-parser.js';
 import * as utils from '../utils.js';
 
@@ -163,9 +161,9 @@ export class Dex2OatCompiler extends BaseCompiler {
 
         // Instantiate D8 compiler, which will in turn instantiate a Java or
         // Kotlin compiler based on the current language.
-        const d8Compiler = unwrap(
-            global.handler_config.compileHandler.findCompiler(this.lang.id, this.d8Id),
-        ) as D8Compiler;
+        const d8Compiler = global.handler_config.compileHandler.findCompiler(this.lang.id, this.d8Id) as
+            | D8Compiler
+            | undefined;
         if (!d8Compiler) {
             return {
                 ...this.handleUserError(
@@ -618,9 +616,12 @@ export class Dex2OatCompiler extends BaseCompiler {
                 const rawCfgText = await fs.readFile(classesCfg, {encoding: 'utf8'});
                 methodsAndOffsetsToDexPcs = this.passDumpParser.parsePassDumpsForDexPcs(rawCfgText.split(/\n/));
             } catch (e) {
-                // This is expected if this is running in a test. If this fails
-                // for another reason, we just won't see line highlights.
-                logger.warn('classes.cfg is missing, source lines will not be highlighted.');
+                // This is expected if this is running in a test. If this fails for another reason, we just won't see
+                // line highlights.
+                segments.push({
+                    text: `classes.cfg is missing, source lines will not be highlighted: ${e}`,
+                    source: null,
+                });
             }
 
             const dexPcsToLines: Record<string, Record<number, number>> = {};
@@ -628,11 +629,15 @@ export class Dex2OatCompiler extends BaseCompiler {
                 const files = await fs.readdir(this.cwd);
                 const smaliFiles = files.filter(f => f.endsWith('.smali'));
                 for (const smaliFile of smaliFiles) {
-                    const rawSmaliText = await fs.readFile(path.join(this.cwd, smaliFile), {encoding: 'utf8'});
+                    const rawSmaliText = await fs.readFile(path.join(this.cwd, smaliFile), 'utf-8');
                     this.parseSmaliForLineNumbers(dexPcsToLines, rawSmaliText.split(/\n/));
                 }
             } catch (e) {
                 // Same case as above.
+                segments.push({
+                    text: `*.smali is missing, source lines will not be highlighted: ${e}`,
+                    source: null,
+                });
             }
 
             segments.push(
@@ -668,7 +673,7 @@ export class Dex2OatCompiler extends BaseCompiler {
                         const offsetToDexPc = methodsAndOffsetsToDexPcs[method];
                         const dexPc = offsetToDexPc ? offsetToDexPc[relativeOffset] : -1;
                         const source =
-                            Number.isInteger(dexPc) && dexPc >= 0
+                            Number.isInteger(dexPc) && dexPc >= 0 && dexPcsToLines[method]
                                 ? {file: null, line: dexPcsToLines[method][dexPc]}
                                 : null;
                         segments.push({


Test patch:
diff --git a/test/android-tests.ts b/test/android-tests.ts
index d69526c75..90484cb68 100644
--- a/test/android-tests.ts
+++ b/test/android-tests.ts
@@ -167,7 +167,7 @@ describe('dex2oat', () => {
         compiler.fullOutput = fullOutput;
 
         // The "result" of running oatdump.
-        const asm = [{text: fs.readFileSync(`${baseFolder}/oatdump.asm`).toString()}];
+        const asm = [{text: fs.readFileSync(`${baseFolder}/oatdump.asm`, 'utf-8')}];
         const objdumpResult = {
             asm,
         };
@@ -178,8 +178,8 @@ describe('dex2oat', () => {
         // fullOutput results in no processing, with the entire oatdump text
         // being returned as one long string.
         const output = fullOutput
-            ? [fs.readFileSync(`${baseFolder}/oatdump.asm`).toString()]
-            : utils.splitLines(fs.readFileSync(`${baseFolder}/output.asm`).toString());
+            ? [fs.readFileSync(`${baseFolder}/oatdump.asm`, 'utf-8')]
+            : utils.splitLines(fs.readFileSync(`${baseFolder}/output.asm`, 'utf-8'));
         const expectedSegments = output.map(line => {
             return {
                 text: line,
diff --git a/test/android/java/output.asm b/test/android/java/output.asm
index 718ce2732..9ddda72b6 100644
--- a/test/android/java/output.asm
+++ b/test/android/java/output.asm
@@ -1,3 +1,5 @@
+classes.cfg is missing, source lines will not be highlighted: Error: ENOENT: no such file or directory, open 'classes.cfg'
+*.smali is missing, source lines will not be highlighted: Error: ENOENT: no such file or directory, scandir ''
 Instruction set:          Arm64
 Instruction set features: a53,crc,-lse,-fp16,-dotprod,-sve
 Compiler filter:          speed
diff --git a/test/android/kotlin/output.asm b/test/android/kotlin/output.asm
index 556ee1b70..b9baeb390 100644
--- a/test/android/kotlin/output.asm
+++ b/test/android/kotlin/output.asm
@@ -1,3 +1,5 @@
+classes.cfg is missing, source lines will not be highlighted: Error: ENOENT: no such file or directory, open 'classes.cfg'
+*.smali is missing, source lines will not be highlighted: Error: ENOENT: no such file or directory, scandir ''
 Instruction set:          Arm64
 Instruction set features: a53,crc,-lse,-fp16,-dotprod,-sve
 Compiler filter:          speed