blob: b4852a391d062a2ff41bfa43ce86056c684e62d6 [file] [log] [blame]
// 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.errors.DesugaredLibraryMismatchDiagnostic;
import com.android.tools.r8.graph.DexString;
import com.android.tools.r8.utils.Reporter;
import com.android.tools.r8.utils.StringDiagnostic;
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.HashSet;
import java.util.Map.Entry;
import java.util.Set;
/** 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 PG_MAP_ID = "pg-map-id";
public static final String R8_MODE = "r8-mode";
private static final String NO_LIBRARY_DESUGARING = "<no-library-desugaring>";
public enum Tool {
D8,
R8,
L8,
Relocator;
public static Tool[] valuesR8andD8() {
return new Tool[] {Tool.D8, Tool.R8};
}
}
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 static void checkCompatibleDesugaredLibrary(Set<Marker> markers, Reporter reporter) {
if (markers.size() <= 1) {
return;
}
// In L8 compilation, the compilation has two markers, a L8 marker, which has a desugared
// library property, and either a D8 or a R8 marker, which has no desugared library property.
// In other compilations, the desugared library versions have to be consistent.
Set<String> desugaredLibraryIdentifiers = new HashSet<>();
for (Marker marker : markers) {
if (marker.tool == Tool.L8) {
assert marker.getDesugaredLibraryIdentifiers().length > 0;
assert markers.stream()
.allMatch(m -> m.tool == Tool.L8 || m.getDesugaredLibraryIdentifiers().length == 0);
} else {
String[] identifiers = marker.getDesugaredLibraryIdentifiers();
String identifier;
switch (identifiers.length) {
case 0:
identifier = NO_LIBRARY_DESUGARING;
break;
case 1:
identifier = identifiers[0];
break;
default:
// To be implemented once D8/R8 compilation supports multiple desugared libraries.
throw reporter.fatalError(
new StringDiagnostic(
"Merging program compiled with multiple desugared libraries."));
}
if (marker.isDesugared()) {
desugaredLibraryIdentifiers.add(identifier);
} else {
assert identifier.equals(NO_LIBRARY_DESUGARING);
}
}
}
if (desugaredLibraryIdentifiers.size() > 1) {
reporter.error(new DesugaredLibraryMismatchDiagnostic(desugaredLibraryIdentifiers));
}
}
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 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;
}
@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();
}
// Try to parse str as a marker.
// Returns null if parsing fails.
public static Marker parse(DexString dexString) {
if (dexString.size > 2
&& dexString.content[0] == PREFIX_CHAR
&& dexString.content[1] == PREFIX_CHAR) {
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;
}
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;
}
}