| // Copyright (c) 2017, the Rex 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 com.android.tools.r8.CompilationMode; |
| import com.android.tools.r8.graph.DexItemFactory; |
| import com.android.tools.r8.graph.DexString; |
| import com.google.gson.JsonArray; |
| import com.google.gson.JsonElement; |
| import com.google.gson.JsonObject; |
| import com.google.gson.JsonParser; |
| import com.google.gson.JsonSyntaxException; |
| import java.util.Comparator; |
| import java.util.Map.Entry; |
| |
| /** Abstraction for hidden dex marker intended for the main dex file. */ |
| public class Marker { |
| |
| public static final String VERSION = "version"; |
| public static final String MIN_API = "min-api"; |
| public static final String DESUGARED_LIBRARY_IDENTIFIERS = "desugared-library-identifiers"; |
| public static final String SHA1 = "sha-1"; |
| public static final String COMPILATION_MODE = "compilation-mode"; |
| public static final String HAS_CHECKSUMS = "has-checksums"; |
| public static final String BACKEND = "backend"; |
| public static final String PG_MAP_ID = "pg-map-id"; |
| public static final String R8_MODE = "r8-mode"; |
| private static final String NO_LIBRARY_DESUGARING = "<no-library-desugaring>"; |
| private static final String ANDROID_PLATFORM_BUILD = "platform"; |
| |
| public enum Tool { |
| D8, |
| R8, |
| L8, |
| Relocator, |
| TraceReferences; |
| |
| public static Tool[] valuesR8andD8() { |
| return new Tool[] {Tool.D8, Tool.R8}; |
| } |
| } |
| |
| public enum Backend { |
| CF, |
| DEX |
| } |
| |
| private static final char PREFIX_CHAR = '~'; |
| private static final String PREFIX = "~~"; |
| private static final String D8_PREFIX = PREFIX + Tool.D8 + "{"; |
| private static final String R8_PREFIX = PREFIX + Tool.R8 + "{"; |
| private static final String L8_PREFIX = PREFIX + Tool.L8 + "{"; |
| |
| private final JsonObject jsonObject; |
| private final Tool tool; |
| |
| public Marker(Tool tool) { |
| this(tool, new JsonObject()); |
| } |
| |
| private Marker(Tool tool, JsonObject jsonObject) { |
| this.tool = tool; |
| this.jsonObject = jsonObject; |
| } |
| |
| public Tool getTool() { |
| return tool; |
| } |
| |
| public boolean isD8() { |
| return tool == Tool.D8; |
| } |
| |
| public boolean isR8() { |
| return tool == Tool.R8; |
| } |
| |
| public boolean isL8() { |
| return tool == Tool.L8; |
| } |
| |
| public boolean isRelocator() { |
| return tool == Tool.Relocator; |
| } |
| |
| public String getVersion() { |
| return jsonObject.get(VERSION).getAsString(); |
| } |
| |
| public Marker setVersion(String version) { |
| assert !jsonObject.has(VERSION); |
| jsonObject.addProperty(VERSION, version); |
| return this; |
| } |
| |
| public boolean isDesugared() { |
| // For both DEX and CF output from D8 and R8 a min-api setting implies that the code has been |
| // desugared, as even the highest min-api require desugaring of lambdas. |
| return hasMinApi(); |
| } |
| |
| public boolean hasMinApi() { |
| return jsonObject.has(MIN_API); |
| } |
| |
| public Long getMinApi() { |
| return jsonObject.get(MIN_API).getAsLong(); |
| } |
| |
| public Marker setMinApi(long minApi) { |
| assert !jsonObject.has(MIN_API); |
| jsonObject.addProperty(MIN_API, minApi); |
| return this; |
| } |
| |
| public boolean hasDesugaredLibraryIdentifiers() { |
| return jsonObject.has(DESUGARED_LIBRARY_IDENTIFIERS); |
| } |
| |
| public String[] getDesugaredLibraryIdentifiers() { |
| if (jsonObject.has(DESUGARED_LIBRARY_IDENTIFIERS)) { |
| JsonArray array = jsonObject.get(DESUGARED_LIBRARY_IDENTIFIERS).getAsJsonArray(); |
| String[] identifiers = new String[array.size()]; |
| for (int i = 0; i < array.size(); i++) { |
| identifiers[i] = array.get(i).getAsString(); |
| } |
| return identifiers; |
| } |
| return new String[0]; |
| } |
| |
| public Marker setDesugaredLibraryIdentifiers(String... identifiers) { |
| assert !jsonObject.has(DESUGARED_LIBRARY_IDENTIFIERS); |
| JsonArray jsonIdentifiers = new JsonArray(); |
| for (String identifier : identifiers) { |
| jsonIdentifiers.add(identifier); |
| } |
| jsonObject.add(DESUGARED_LIBRARY_IDENTIFIERS, jsonIdentifiers); |
| return this; |
| } |
| |
| public String getSha1() { |
| return jsonObject.get(SHA1).getAsString(); |
| } |
| |
| public Marker setSha1(String sha1) { |
| assert !jsonObject.has(SHA1); |
| jsonObject.addProperty(SHA1, sha1); |
| return this; |
| } |
| |
| public String getCompilationMode() { |
| return jsonObject.get(COMPILATION_MODE).getAsString(); |
| } |
| |
| public Marker setCompilationMode(CompilationMode mode) { |
| assert !jsonObject.has(COMPILATION_MODE); |
| jsonObject.addProperty(COMPILATION_MODE, mode.toString().toLowerCase()); |
| return this; |
| } |
| |
| public boolean hasBackend() { |
| return jsonObject.has(BACKEND); |
| } |
| |
| public String getBackend() { |
| if (!hasBackend()) { |
| // Before adding backend we would always compile to dex if min-api was specified. |
| return hasMinApi() ? Backend.DEX.name().toLowerCase() : Backend.CF.name().toLowerCase(); |
| } |
| return jsonObject.get(BACKEND).getAsString(); |
| } |
| |
| public boolean isCfBackend() { |
| return getBackend().equals(Backend.CF.name().toLowerCase()); |
| } |
| |
| public boolean isDexBackend() { |
| return getBackend().equals(Backend.DEX.name().toLowerCase()); |
| } |
| |
| public Marker setBackend(Backend backend) { |
| assert !hasBackend(); |
| jsonObject.addProperty(BACKEND, backend.name().toLowerCase()); |
| return this; |
| } |
| |
| public boolean getHasChecksums() { |
| return jsonObject.get(HAS_CHECKSUMS).getAsBoolean(); |
| } |
| |
| public Marker setHasChecksums(boolean hasChecksums) { |
| assert !jsonObject.has(HAS_CHECKSUMS); |
| jsonObject.addProperty(HAS_CHECKSUMS, hasChecksums); |
| return this; |
| } |
| |
| public String getPgMapId() { |
| return jsonObject.get(PG_MAP_ID).getAsString(); |
| } |
| |
| public Marker setPgMapId(String pgMapId) { |
| assert !jsonObject.has(PG_MAP_ID); |
| jsonObject.addProperty(PG_MAP_ID, pgMapId); |
| return this; |
| } |
| |
| public String getR8Mode() { |
| return jsonObject.get(R8_MODE).getAsString(); |
| } |
| |
| public Marker setR8Mode(String r8Mode) { |
| assert !jsonObject.has(R8_MODE); |
| jsonObject.addProperty(R8_MODE, r8Mode); |
| return this; |
| } |
| |
| public boolean isAndroidPlatformBuild() { |
| return jsonObject.has(ANDROID_PLATFORM_BUILD) |
| && jsonObject.get(ANDROID_PLATFORM_BUILD).getAsBoolean(); |
| } |
| |
| public Marker setAndroidPlatformBuild() { |
| assert !jsonObject.has(ANDROID_PLATFORM_BUILD); |
| jsonObject.addProperty(ANDROID_PLATFORM_BUILD, true); |
| return this; |
| } |
| |
| @Override |
| public String toString() { |
| // In order to make printing of markers deterministic we sort the entries by key. |
| final JsonObject sortedJson = new JsonObject(); |
| jsonObject.entrySet().stream() |
| .sorted(Comparator.comparing(Entry::getKey)) |
| .forEach(entry -> sortedJson.add(entry.getKey(), entry.getValue())); |
| return PREFIX + tool + sortedJson; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof Marker) { |
| Marker other = (Marker) obj; |
| return (tool == other.tool) && jsonObject.equals(other.jsonObject); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return tool.hashCode() + 3 * jsonObject.hashCode(); |
| } |
| |
| public DexString toDexString(DexItemFactory factory) { |
| return factory.createString(toString()); |
| } |
| |
| // Try to parse str as a marker. |
| // Returns null if parsing fails. |
| public static Marker parse(DexString dexString) { |
| if (hasMarkerPrefix(dexString.content)) { |
| String str = dexString.toString(); |
| if (str.startsWith(D8_PREFIX)) { |
| return internalParse(Tool.D8, str.substring(D8_PREFIX.length() - 1)); |
| } |
| if (str.startsWith(R8_PREFIX)) { |
| return internalParse(Tool.R8, str.substring(R8_PREFIX.length() - 1)); |
| } |
| if (str.startsWith(L8_PREFIX)) { |
| return internalParse(Tool.L8, str.substring(L8_PREFIX.length() - 1)); |
| } |
| } |
| return null; |
| } |
| |
| public static boolean hasMarkerPrefix(byte[] content) { |
| return content.length > 2 && content[0] == PREFIX_CHAR && content[1] == PREFIX_CHAR; |
| } |
| |
| private static Marker internalParse(Tool tool, String str) { |
| try { |
| JsonElement result = new JsonParser().parse(str); |
| if (result.isJsonObject()) { |
| return new Marker(tool, result.getAsJsonObject()); |
| } |
| } catch (JsonSyntaxException e) { |
| // Fall through. |
| } |
| return null; |
| } |
| } |