Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# [Unreleased]
## Fixed
* Fixes #346 - Case-sensitivity not respected in SSH path mapping - PR #352 (@brownts)
* Fixes #342 - Local variables not displayed more than 2 stack frames deep - PR #345 (@brownts)

[Unreleased]: https://github.com/WebFreak001/code-debug/compare/v0.26.0...HEAD
Expand Down
49 changes: 7 additions & 42 deletions src/mibase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode, M
import { MINode } from './backend/mi_parse';
import { expandValue, isExpandable } from './backend/gdb_expansion';
import { MI2 } from './backend/mi2/mi2';
import { posix } from "path";
import * as systemPath from "path";
import * as net from "net";
import * as os from "os";
import * as fs from "fs";
import { SourceFileMap } from "./source_file_map";

class ExtendedVariable {
constructor(public name, public options) {
Expand Down Expand Up @@ -37,7 +37,7 @@ export class MI2DebugSession extends DebugSession {
protected initialRunCommand: RunCommand;
protected stopAtEntry: boolean | string;
protected isSSH: boolean;
protected sourceFileMap: Map<string,string>;
protected sourceFileMap: SourceFileMap;
protected started: boolean;
protected crashed: boolean;
protected miDebugger: MI2;
Expand Down Expand Up @@ -231,16 +231,7 @@ export class MI2DebugSession extends DebugSession {
let path = args.source.path;
if (this.isSSH) {
// convert local path to ssh path
if (path.indexOf("\\") != -1)
path = path.replace(/\\/g, "/").toLowerCase();
// ideCWD is the local path, gdbCWD is the ssh path, both had the replacing \ -> / done up-front
for (let [ideCWD, gdbCWD] of this.sourceFileMap) {
if (path.startsWith(ideCWD)) {
path = posix.relative(ideCWD, path);
path = posix.join(gdbCWD, path); // we combined a guaranteed path with relative one (works with GDB both on GNU/Linux and Win32)
break;
}
}
path = this.sourceFileMap.toRemotePath(path);
}
const all = args.breakpoints.map(brk => {
return this.miDebugger.addBreakPoint({ file: path, line: brk.line, condition: brk.condition, countCondition: brk.hitCondition });
Expand Down Expand Up @@ -301,16 +292,7 @@ export class MI2DebugSession extends DebugSession {
if (path) {
if (this.isSSH) {
// convert ssh path to local path
if (path.indexOf("\\") != -1)
path = path.replace(/\\/g, "/").toLowerCase();
// ideCWD is the local path, gdbCWD is the ssh path, both had the replacing \ -> / done up-front
for (let [ideCWD, gdbCWD] of this.sourceFileMap) {
if (path.startsWith(gdbCWD)) {
path = posix.relative(gdbCWD, path); // only operates on "/" paths
path = systemPath.resolve(ideCWD, path); // will do the conversion to "\" on Win32
break;
}
}
path = this.sourceFileMap.toLocalPath(path);
} else if (process.platform === "win32") {
if (path.startsWith("\\cygdrive\\") || path.startsWith("/cygdrive/")) {
path = path[10] + ":" + path.substr(11); // replaces /cygdrive/c/foo/bar.txt with c:/foo/bar.txt
Expand Down Expand Up @@ -745,28 +727,11 @@ export class MI2DebugSession extends DebugSession {
this.sendResponse(response);
}

private addSourceFileMapEntry(gdbCWD :string, ideCWD : string): void {
// if it looks like a Win32 path convert to "/"-style for comparisions and to all-lower-case
if (ideCWD.indexOf("\\") != -1)
ideCWD = ideCWD.replace(/\\/g, "/").toLowerCase();
if (!ideCWD.endsWith("/"))
ideCWD = ideCWD + "/"
// ensure that we only replace complete paths
if (gdbCWD.indexOf("\\") != -1)
gdbCWD = gdbCWD.replace(/\\/g, "/").toLowerCase();
if (!gdbCWD.endsWith("/"))
gdbCWD = gdbCWD + "/"
this.sourceFileMap.set(ideCWD, gdbCWD);
}

protected setSourceFileMap(configMap: { [index: string]: string }, fallbackGDB :string, fallbackIDE : string): void {
this.sourceFileMap = new Map<string, string>();
protected setSourceFileMap(configMap: { [index: string]: string }, fallbackGDB: string, fallbackIDE: string): void {
if (configMap === undefined) {
this.addSourceFileMapEntry(fallbackGDB, fallbackIDE);
this.sourceFileMap = new SourceFileMap({[fallbackGDB]: fallbackIDE});
} else {
for (let [gdbPath, localPath] of Object.entries(configMap)) {
this.addSourceFileMapEntry(gdbPath, localPath);
}
this.sourceFileMap = new SourceFileMap(configMap);
}
}

Expand Down
55 changes: 55 additions & 0 deletions src/path_kind.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as Path from "path";

export abstract class PathKind {
protected abstract readonly path: typeof Path.posix | typeof Path.win32;
public abstract readonly caseSensitive: boolean;

// The path.posix.normalize routine will not convert Win32 path separators
// to POSIX separators, so we explictily convert any Win32 path separators
// to POSIX style separators. The path.win32.normalize routine will accept
// either Win32 or POSIX style separators and will normalize them to the
// Win32 style. Thus, if we convert all path separators to POSIX style and
// then normalize, this will work for both systems.
public normalize(p: string): string {
return this.path.normalize(p.replace(/\\/g, "/"));
}

public normalizeDir(p: string): string {
p = this.normalize(p);
if (! p.endsWith(this.path.sep))
p = this.path.join(p, this.path.sep);
return p;
}

public join(...paths: string[]): string {
return this.normalize(this.path.join(...paths));
}

public isAbsolute(p: string): boolean {
return this.path.isAbsolute(this.normalize(p));
}
}

export class PathWin32 extends PathKind {
protected readonly path: typeof Path.posix | typeof Path.win32 = Path.win32;
public readonly caseSensitive: boolean = false;
private static instance: PathWin32;
private constructor() { super(); }
public static getInstance(): PathWin32 {
if (! this.instance)
this.instance = new PathWin32();
return this.instance;
}
}

export class PathPosix extends PathKind {
protected readonly path: typeof Path.posix | typeof Path.win32 = Path.posix;
public readonly caseSensitive: boolean = true;
private static instance: PathPosix;
private constructor() { super(); }
public static getInstance(): PathPosix {
if (! this.instance)
this.instance = new PathPosix();
return this.instance;
}
}
98 changes: 98 additions & 0 deletions src/source_file_map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { PathKind, PathWin32, PathPosix } from "./path_kind";

interface Mapping {
"remote": string;
"local": string;
}

export class SourceFileMap {
private sortedMappings: { [key in keyof Mapping]: Mapping[] } = {remote: [], local: []};
private nativePath: PathKind;

constructor (map: { [index: string]: string }) {
const mappings: Mapping[] = [];
this.nativePath = this.getNativePath();
for (let [remotePrefix, localPrefix] of Object.entries(map)) {
// Normalize local path, adding trailing separator if missing.
localPrefix = this.nativePath.normalizeDir(localPrefix);

// Try to detect remote path.
const debuggerPath: PathKind = SourceFileMap.toPathKind(remotePrefix);
// Normalize remote path, adding trailing separator if missing.
remotePrefix = debuggerPath.normalizeDir(remotePrefix);

mappings.push({remote: remotePrefix, local: localPrefix});
}

// Sort with longest paths first in case some paths are subsets, so that
// we match the most appropriate (e.g., with path prefixes of '/home'
// and '/home/foo', and a complete path of '/home/foo/bar.c', we should
// match the '/home/foo' path prefix instead of '/home'.
this.sortedMappings.local = [...mappings].sort((a: Mapping, b: Mapping) => b.local.length - a.local.length);
this.sortedMappings.remote = [...mappings].sort((a: Mapping, b: Mapping) => b.remote.length - a.remote.length);
}

// The native path selection is isolated here to allow for easy unit testing
// allowing non-native path types to be tested by overriding this method in
// a subclass in the test harness.
protected getNativePath(): PathKind {
if (process.platform == "win32")
return PathWin32.getInstance();
else
return PathPosix.getInstance();
}

private static toPathKind(unknownPath: string): PathKind {
const pathPosix: PathKind = PathPosix.getInstance();
const pathWin32: PathKind = PathWin32.getInstance();
return pathPosix.isAbsolute(unknownPath) ? pathPosix : pathWin32;
}

private pathMatch(key: keyof Mapping, caseSensitive: boolean, path: string): Mapping | undefined {
for (const mapping of this.sortedMappings[key]) {
let matched: boolean;

if (caseSensitive)
matched = path.startsWith(mapping[key]);
else
matched = path.toLowerCase().startsWith(mapping[key].toLowerCase());

if (matched)
return mapping;
}

return undefined;
}

public toLocalPath(remotePath: string): string {
// Try to detect remote path.
const debuggerPath: PathKind = SourceFileMap.toPathKind(remotePath);
const normalizedRemotePath: string = debuggerPath.normalize(remotePath);
const mapping: Mapping | undefined =
this.pathMatch("remote", debuggerPath.caseSensitive, normalizedRemotePath);

if (mapping) {
const pathSuffix = normalizedRemotePath.substring(mapping.remote.length);
return this.nativePath.join(mapping.local, pathSuffix);
}

// No mapping found, so return unmapped path.
return remotePath;
}

public toRemotePath (localPath: string): string {
const normalizedLocalPath = this.nativePath.normalize(localPath);
const mapping: Mapping | undefined =
this.pathMatch("local", this.nativePath.caseSensitive, normalizedLocalPath);

if (mapping) {
const pathSuffix = normalizedLocalPath.substring(mapping.local.length);
// Try to detect remote path.
const debuggerPath = SourceFileMap.toPathKind(mapping.remote);
return debuggerPath.join(mapping.remote, pathSuffix);
}

// No mapping found, so return unmapped path.
return localPath;
}
}
79 changes: 79 additions & 0 deletions src/test/suite/path_kind.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import * as assert from 'assert';
import { PathKind, PathWin32, PathPosix } from "../../path_kind";

suite("Path Kind", () => {
const pathWin32: PathKind = PathWin32.getInstance();
const pathPosix: PathKind = PathPosix.getInstance();
test("Normalize", () => {
assert.strictEqual(pathWin32.normalize("C:/foo/bar"), "C:\\foo\\bar");
assert.strictEqual(pathWin32.normalize("C:\\foo\\bar"), "C:\\foo\\bar");
assert.strictEqual(pathWin32.normalize("C:/foo/bar/"), "C:\\foo\\bar\\");
assert.strictEqual(pathWin32.normalize("C:\\foo\\bar\\"), "C:\\foo\\bar\\");
assert.strictEqual(pathWin32.normalize("C:\\foo\\bar\\.."), "C:\\foo");
assert.strictEqual(pathWin32.normalize("C:\\foo\\bar\\..\\"), "C:\\foo\\");
assert.strictEqual(pathWin32.normalize("C:\\foo\\..\\bar"), "C:\\bar");
assert.strictEqual(pathWin32.normalize("C:\\foo\\..\\bar\\"), "C:\\bar\\");

assert.strictEqual(pathPosix.normalize("\\home\\foo\\bar"), "/home/foo/bar");
assert.strictEqual(pathPosix.normalize("/home/foo/bar"), "/home/foo/bar");
assert.strictEqual(pathPosix.normalize("\\home\\foo\\bar\\"), "/home/foo/bar/");
assert.strictEqual(pathPosix.normalize("/home/foo/bar/"), "/home/foo/bar/");
assert.strictEqual(pathPosix.normalize("/home/foo/bar/.."), "/home/foo");
assert.strictEqual(pathPosix.normalize("/home/foo/bar/../"), "/home/foo/");
assert.strictEqual(pathPosix.normalize("/home/foo/../bar"), "/home/bar");
assert.strictEqual(pathPosix.normalize("/home/foo/../bar/"), "/home/bar/");
});
test("Normalize Directory", () => {
assert.strictEqual(pathWin32.normalizeDir("C:/foo/bar"), "C:\\foo\\bar\\");
assert.strictEqual(pathWin32.normalizeDir("C:\\foo\\bar"), "C:\\foo\\bar\\");
assert.strictEqual(pathWin32.normalizeDir("C:/foo/bar/"), "C:\\foo\\bar\\");
assert.strictEqual(pathWin32.normalizeDir("C:\\foo\\bar\\"), "C:\\foo\\bar\\");
assert.strictEqual(pathWin32.normalizeDir("C:\\foo\\bar\\.."), "C:\\foo\\");
assert.strictEqual(pathWin32.normalizeDir("C:\\foo\\bar\\..\\"), "C:\\foo\\");
assert.strictEqual(pathWin32.normalizeDir("C:\\foo\\..\\bar"), "C:\\bar\\");
assert.strictEqual(pathWin32.normalizeDir("C:\\foo\\..\\bar\\"), "C:\\bar\\");

assert.strictEqual(pathPosix.normalizeDir("\\home\\foo\\bar"), "/home/foo/bar/");
assert.strictEqual(pathPosix.normalizeDir("/home/foo/bar"), "/home/foo/bar/");
assert.strictEqual(pathPosix.normalizeDir("\\home\\foo\\bar\\"), "/home/foo/bar/");
assert.strictEqual(pathPosix.normalizeDir("/home/foo/bar/"), "/home/foo/bar/");
assert.strictEqual(pathPosix.normalizeDir("/home/foo/bar/.."), "/home/foo/");
assert.strictEqual(pathPosix.normalizeDir("/home/foo/bar/../"), "/home/foo/");
assert.strictEqual(pathPosix.normalizeDir("/home/foo/../bar"), "/home/bar/");
assert.strictEqual(pathPosix.normalizeDir("/home/foo/../bar/"), "/home/bar/");
});
test("Join", () => {
assert.strictEqual(pathWin32.join("C:/foo", "bar/baz.c"), "C:\\foo\\bar\\baz.c");
assert.strictEqual(pathWin32.join("C:\\foo", "bar\\baz.c"), "C:\\foo\\bar\\baz.c");
assert.strictEqual(pathWin32.join("C:\\foo\\", "\\bar\\baz.c"), "C:\\foo\\bar\\baz.c");
assert.strictEqual(pathWin32.join("C:\\Foo\\", "\\Bar\\baz.c"), "C:\\Foo\\Bar\\baz.c");

assert.strictEqual(pathPosix.join("\\home\\foo", "bar\\baz.c"), "/home/foo/bar/baz.c");
assert.strictEqual(pathPosix.join("/home/foo", "bar/baz.c"), "/home/foo/bar/baz.c");
assert.strictEqual(pathPosix.join("/home/foo/", "/bar/baz.c"), "/home/foo/bar/baz.c");
assert.strictEqual(pathPosix.join("/home/Foo/", "/Bar/baz.c"), "/home/Foo/Bar/baz.c");
});
test("Is Absolute Path", () => {
assert.strictEqual(pathWin32.isAbsolute("C:/foo/bar"), true);
assert.strictEqual(pathWin32.isAbsolute("C:\\foo\\bar"), true);
assert.strictEqual(pathWin32.isAbsolute("C:/foo/bar/"), true);
assert.strictEqual(pathWin32.isAbsolute("C:\\foo\\bar\\"), true);
assert.strictEqual(pathWin32.isAbsolute("C:\\foo\\..\\bar"), true);
assert.strictEqual(pathWin32.isAbsolute("C:\\foo\\..\\bar\\"), true);
assert.strictEqual(pathWin32.isAbsolute("foo/bar"), false);
assert.strictEqual(pathWin32.isAbsolute("foo\\bar"), false);
assert.strictEqual(pathWin32.isAbsolute("foo/bar/"), false);
assert.strictEqual(pathWin32.isAbsolute("foo\\bar\\"), false);

assert.strictEqual(pathPosix.isAbsolute("\\home\\foo\\bar"), true);
assert.strictEqual(pathPosix.isAbsolute("/home/foo/bar"), true);
assert.strictEqual(pathPosix.isAbsolute("\\home\\foo\\bar\\"), true);
assert.strictEqual(pathPosix.isAbsolute("/home/foo/bar/"), true);
assert.strictEqual(pathPosix.isAbsolute("/home/foo/../bar"), true);
assert.strictEqual(pathPosix.isAbsolute("/home/foo/../bar/"), true);
assert.strictEqual(pathPosix.isAbsolute("foo\\bar"), false);
assert.strictEqual(pathPosix.isAbsolute("foo/bar"), false);
assert.strictEqual(pathPosix.isAbsolute("foo\\bar\\"), false);
assert.strictEqual(pathPosix.isAbsolute("foo/bar/"), false);
});
});
Loading