All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.robovm.compiler.plugin.debug.kotlin.KotlinTools Maven / Gradle / Ivy

The newest version!
package org.robovm.compiler.plugin.debug.kotlin;

import org.robovm.compiler.config.Config;
import org.robovm.compiler.plugin.debug.DebugInformationPlugin;

import java.util.ArrayList;
import java.util.List;

/**
 * @author dkimitsa
 * helper file to deal with kotlin stuff
 */
public class KotlinTools {

    // VO to keep range information
    static class Range {
        final int originalIndex;
        final int targetIndexStart;
        final int targetIndexEnd;

        Range(int originalIndex, int targetIndexStart, int targetIndexEnd) {
            this.originalIndex = originalIndex;
            this.targetIndexStart = targetIndexStart;
            this.targetIndexEnd = targetIndexEnd;
        }
    }

    // string constants to parse
    private final static String DEBUG_SECTION = "*S KotlinDebug";
    private final static String FILE_SECTION = "*F";
    private final static String LINE_SECTION = "*L";
    private final static String END = "*E";

    /**
     * parses SMAP data and return line number mapper to handle kotlin remaps
     * parsing is based on original kotlin sources:
     * kotlin/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SMAPParser.kt
     */
    public static DebugInformationPlugin.LineNumberMapper parseSMAP(Config config, byte[] smapData, String internalName) {
        String mappingInfo = new String(smapData);
        List lineMappings = new ArrayList<>();

        int debugSectionStart = mappingInfo.indexOf(DEBUG_SECTION);
        if (debugSectionStart < 0)
            return DebugInformationPlugin.LineNumberMapper.DIRECT;

        // interested only in debug section
        mappingInfo = mappingInfo.substring(debugSectionStart + DEBUG_SECTION.length());

        int fileSectionStart = mappingInfo.indexOf(FILE_SECTION) + FILE_SECTION.length();
        int lineSectionAnchor = mappingInfo.indexOf(LINE_SECTION);

        String files = mappingInfo.substring(fileSectionStart, lineSectionAnchor);
        String[] fileEntries = files.trim().split("\\+");

        // look only for mapping of own file
        int ownFileIndex = -1;
        for (String fileDeclaration : fileEntries) {
            String fileInternalName = fileDeclaration.trim();
            if (fileInternalName.isEmpty())
                continue;

            int indexEnd = fileInternalName.indexOf(' ');
            int index = Integer.parseInt(fileInternalName.substring(0, indexEnd));
            int newLine = fileInternalName.indexOf('\n');

            fileInternalName = fileInternalName.substring(newLine + 1).trim();
            if (fileInternalName.equals(internalName)) {
                ownFileIndex = index;
                break;
            }
        }


        if (ownFileIndex == -1)
            return DebugInformationPlugin.LineNumberMapper.DIRECT;

        String[] lines = mappingInfo.substring(lineSectionAnchor + LINE_SECTION.length(), mappingInfo.indexOf(END)).trim().split("\\n");
        int minTargetIndex = Integer.MAX_VALUE;
        int maxTargetIndex = Integer.MIN_VALUE;
        for (String lineMapping : lines) {
            try {
                Range range = parseLineMap(ownFileIndex, lineMapping);

                // get min/max values for optimization
                if (range.targetIndexStart < minTargetIndex)
                    minTargetIndex = range.targetIndexStart;
                if (range.targetIndexEnd > maxTargetIndex)
                    maxTargetIndex = range.targetIndexEnd;

                lineMappings.add(range);
            } catch (Throwable e) {
                if (config.getHome().isDev())
                    config.getLogger().warn(e.getLocalizedMessage());
            }
        }

        if (lineMappings.size() == 1) {
            // simple case with one entry
            Range r = lineMappings.get(0);
            int targetIndexStart = r.targetIndexStart;
            int targetIndexEnd = r.targetIndexEnd;
            int originalIndex = r.originalIndex;
            return (l) -> {
                if (l >= targetIndexStart && l <= targetIndexEnd)
                    return originalIndex + l - targetIndexStart;
                else
                    return l;
            };
        } else if (lineMappings.size() > 1) {
            // complex case, check all range entries

            int minLine = minTargetIndex;
            int maxLine = maxTargetIndex;
            return (l) -> {
                if (l >= minLine && l <= maxLine) {
                    for (Range r : lineMappings) {
                        if (l >= r.targetIndexStart && l <= r.targetIndexEnd)
                            return r.originalIndex + l - r.targetIndexStart;
                    }
                }

                // not found
                return l;
            };
        }

        // there seems to be no entries at all, return direct mapping
        return DebugInformationPlugin.LineNumberMapper.DIRECT;
    }

    static Range parseLineMap(int expectedFileIndex, String lineMapping) {
        // In KotlinDebug, they use `1#2,3:4` to mean "map lines 4..6 to line 1 of #2", when in reality (and i
        // the non-debug stratum) this maps lines 4..6 to lines 1..3. The correct syntax is `1#2:4,3`.
        // multiple options:
        // case 1: "$source#$fileId:$dest\n"
        // case 2: "$source#$fileId:$dest,$range"
        // case 3: "$source#$fileId,$range:$dest"
        int fileSeparatorIdx = lineMapping.indexOf('#');
        if (fileSeparatorIdx <= 0)
            throw new IllegalArgumentException("fileId(#) separator not found in " + lineMapping);

        // get positions of other separators
        int semicolonIdx = lineMapping.indexOf(':', fileSeparatorIdx + 1);
        if (semicolonIdx < fileSeparatorIdx + 2)
            throw new IllegalArgumentException("fileId(:) separator not found or empty in " + lineMapping);
        int commaIdx = lineMapping.indexOf(',', fileSeparatorIdx + 1);
        if (commaIdx > 0 && commaIdx < fileSeparatorIdx + 2)
            throw new IllegalArgumentException("range(,) separator is empty in " + lineMapping);

        // parse values
        int originalIndex = Integer.parseInt(lineMapping.substring(0, fileSeparatorIdx));
        int fileIndex;
        int range;
        int dest;

        if (commaIdx < 0) {
            // case 1: "$source#$fileId:$dest\n"
            fileIndex = Integer.parseInt(lineMapping.substring(fileSeparatorIdx + 1, semicolonIdx));
            dest = Integer.parseInt(lineMapping.substring(semicolonIdx + 1));
            range = 1;
        } else {
            if (commaIdx > semicolonIdx + 1) {
                // case 2: "$source#$fileId:$dest,$range"
                fileIndex = Integer.parseInt(lineMapping.substring(fileSeparatorIdx + 1, semicolonIdx));
                dest = Integer.parseInt(lineMapping.substring(semicolonIdx + 1, commaIdx));
                range = Integer.parseInt(lineMapping.substring(commaIdx + 1));
            } else if (semicolonIdx > commaIdx + 1){
                // case 3: "$source#$fileId,$range:$dest"
                fileIndex = Integer.parseInt(lineMapping.substring(fileSeparatorIdx + 1, commaIdx));
                dest = Integer.parseInt(lineMapping.substring(semicolonIdx + 1));
                range = Integer.parseInt(lineMapping.substring(commaIdx + 1, semicolonIdx));
            } else {
                // comma or semicolon next to each other
                throw new IllegalArgumentException("Empty range/dest in " + lineMapping);
            }
        }
        if (fileIndex != expectedFileIndex)
            throw new IllegalArgumentException("fileIndex doesn't match expected " + expectedFileIndex + " in " + lineMapping);

        return new Range(originalIndex, dest, dest + range - 1);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy