blob: 8d6f2d0c4033c480eb15e24684871656a0eaa4f1 [file] [log] [blame]
// Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.dex;
import static com.android.tools.r8.dex.Constants.MAX_VDEX_VERSION;
import static com.android.tools.r8.dex.Constants.MIN_VDEX_VERSION;
import static com.android.tools.r8.dex.Constants.VDEX_FILE_MAGIC_PREFIX;
import static com.android.tools.r8.dex.Constants.VDEX_FILE_VERSION_LENGTH;
import static com.android.tools.r8.dex.Constants.VDEX_NUMBER_OF_DEX_FILES_OFFSET;
import com.android.tools.r8.errors.CompilationError;
import com.android.tools.r8.origin.Origin;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteOrder;
/**
* See runtime/vdex_file.h and runtime/vdex_file.cc in the Art code for the vdex file format.
*/
public class VDexReader extends BinaryReader {
private final int version;
public VDexReader(Origin origin, InputStream stream) throws IOException {
super(origin, ByteStreams.toByteArray(stream));
version = parseMagic(buffer);
if (!supportedVersion(version)) {
throw new CompilationError("Unsupported vdex file version " + version, origin);
}
}
private static boolean supportedVersion(int versionNumber) {
return MIN_VDEX_VERSION <= versionNumber && versionNumber <= MAX_VDEX_VERSION;
}
// Parse the magic header and determine the dex file version.
private int parseMagic(CompatByteBuffer buffer) {
int index = 0;
for (byte prefixByte : VDEX_FILE_MAGIC_PREFIX) {
if (buffer.get(index++) != prefixByte) {
throw new CompilationError("VDex file has invalid header", origin);
}
}
byte[] version = new byte[VDEX_FILE_VERSION_LENGTH];
for (int i = 0; i < VDEX_FILE_VERSION_LENGTH; i++) {
if (!buffer.hasRemaining()) {
throw new CompilationError("Truncated VDex file - unable to read version", origin);
}
version[i] = buffer.get(index++);
}
if (version[VDEX_FILE_VERSION_LENGTH - 1] != '\0') {
throw new CompilationError("VDex file has invalid version number", origin);
}
int versionNumber = 0;
for (int i = 0; i < VDEX_FILE_VERSION_LENGTH - 1;i++) {
if (0x30 <= version[i] && version[i] <= 0x39) {
versionNumber = versionNumber * 10 + version[i] - 0x30;
} else {
throw new CompilationError("VDex file has invalid version number", origin);
}
}
return versionNumber;
}
public static int firstDexOffset(int numberOfDexFiles) {
return Constants.VDEX_CHECKSUM_SECTION_OFFSET
+ numberOfDexFiles * Constants.VDEX_DEX_CHECKSUM_SIZE;
}
@Override
void setByteOrder() {
// Make sure we set the right endian for reading.
buffer.order(ByteOrder.LITTLE_ENDIAN);
int dexFiles = buffer.getInt(VDEX_NUMBER_OF_DEX_FILES_OFFSET);
// Reading a strange number of dex files indicate reading with the wrong endian.
if (dexFiles < 0 || dexFiles > 1000) {
buffer.order(ByteOrder.BIG_ENDIAN);
dexFiles = buffer.getInt(VDEX_NUMBER_OF_DEX_FILES_OFFSET);
assert dexFiles < 0 || dexFiles > 1000;
}
// Make sure we did set the right endian for reading.
int endian = buffer.getInt(firstDexOffset(dexFiles) + Constants.ENDIAN_TAG_OFFSET);
if (endian != Constants.ENDIAN_CONSTANT) {
throw new CompilationError("Unable to determine endianess for reading vdex file.");
}
}
int getVDexVersion() {
return version;
}
}