Skip to content

Commit

Permalink
Add setting for enabling go to decompilation.
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeRobich committed May 11, 2020
1 parent 552fd18 commit 607af72
Show file tree
Hide file tree
Showing 12 changed files with 137 additions and 28 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,12 @@
"default": false,
"description": "Enables support for reading code style, naming convention and analyzer settings from .editorconfig."
},
"omnisharp.enableDecompilationSupport": {
"type": "boolean",
"default": false,
"scope": "machine",
"description": "Enables support for decompiling external references instead of viewing metadata."
},
"razor.plugin.path": {
"type": [
"string",
Expand Down Expand Up @@ -870,6 +876,11 @@
"title": "Report an issue",
"category": "CSharp"
},
{
"command": "csharp.showDecompilationTerms",
"title": "Show the decompiler terms agreement",
"category": "CSharp"
},
{
"command": "extension.showRazorCSharpWindow",
"title": "Show Razor CSharp",
Expand Down
19 changes: 16 additions & 3 deletions src/features/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ import OptionProvider from '../observers/OptionProvider';
import reportIssue from './reportIssue';
import { IMonoResolver } from '../constants/IMonoResolver';
import { getDotnetInfo } from '../utils/getDotnetInfo';
import { getDecompilationAuthorization } from '../omnisharp/decompilationPrompt';

export default function registerCommands(server: OmniSharpServer, platformInfo: PlatformInformation, eventStream: EventStream, optionProvider: OptionProvider, monoResolver: IMonoResolver, packageJSON: any, extensionPath: string): CompositeDisposable {
export default function registerCommands(context: vscode.ExtensionContext, server: OmniSharpServer, platformInfo: PlatformInformation, eventStream: EventStream, optionProvider: OptionProvider, monoResolver: IMonoResolver, packageJSON: any, extensionPath: string): CompositeDisposable {
let disposable = new CompositeDisposable();
disposable.add(vscode.commands.registerCommand('o.restart', () => restartOmniSharp(server)));
disposable.add(vscode.commands.registerCommand('o.restart', async () => restartOmniSharp(context, server, optionProvider)));
disposable.add(vscode.commands.registerCommand('o.pickProjectAndStart', async () => pickProjectAndStart(server, optionProvider)));
disposable.add(vscode.commands.registerCommand('o.showOutput', () => eventStream.post(new ShowOmniSharpChannel())));
disposable.add(vscode.commands.registerCommand('dotnet.restore.project', async () => pickProjectAndDotnetRestore(server, eventStream)));
Expand Down Expand Up @@ -54,10 +55,22 @@ export default function registerCommands(server: OmniSharpServer, platformInfo:
disposable.add(vscode.commands.registerCommand('csharp.clrAdapterExecutableCommand', async (args) => getAdapterExecutionCommand(platformInfo, eventStream, packageJSON, extensionPath)));
disposable.add(vscode.commands.registerCommand('csharp.reportIssue', async () => reportIssue(vscode, eventStream, getDotnetInfo, platformInfo.isValidPlatformForMono(), optionProvider.GetLatestOptions(), monoResolver)));

disposable.add(vscode.commands.registerCommand('csharp.showDecompilationTerms', async () => showDecompilationTerms(context, server, optionProvider)));

return new CompositeDisposable(disposable);
}

function restartOmniSharp(server: OmniSharpServer) {
async function showDecompilationTerms(context: vscode.ExtensionContext, server: OmniSharpServer, optionProvider: OptionProvider) {
// Reset the decompilation authorization so the user will be prompted on restart.
context.workspaceState.update("decompilationAuthorized", undefined);

await restartOmniSharp(context, server, optionProvider);
}

async function restartOmniSharp(context: vscode.ExtensionContext, server: OmniSharpServer, optionProvider: OptionProvider) {
// Update decompilation authorization for this workspace.
server.decompilationAuthorized = await getDecompilationAuthorization(context, optionProvider);

if (server.isRunning()) {
server.restart();
}
Expand Down
4 changes: 4 additions & 0 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import IInstallDependencies from './packageManager/IInstallDependencies';
import { installRuntimeDependencies } from './InstallRuntimeDependencies';
import { isValidDownload } from './packageManager/isValidDownload';
import { BackgroundWorkStatusBarObserver } from './observers/BackgroundWorkStatusBarObserver';
import { getDecompilationAuthorization } from './omnisharp/decompilationPrompt';

export async function activate(context: vscode.ExtensionContext): Promise<CSharpExtensionExports> {

Expand Down Expand Up @@ -154,6 +155,9 @@ export async function activate(context: vscode.ExtensionContext): Promise<CSharp
let installDependencies: IInstallDependencies = async (dependencies: AbsolutePathPackage[]) => downloadAndInstallPackages(dependencies, networkSettingsProvider, eventStream, isValidDownload);
let runtimeDependenciesExist = await ensureRuntimeDependencies(extension, eventStream, platformInfo, installDependencies);

// Prompt to authorize decompilation in this workspace
await getDecompilationAuthorization(context, optionProvider);

// activate language services
let langServicePromise = OmniSharp.activate(context, extension.packageJSON, platformInfo, networkSettingsProvider, eventStream, optionProvider, extension.extensionPath);

Expand Down
49 changes: 29 additions & 20 deletions src/observers/OptionChangeObserver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,45 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { vscode } from "../vscodeAdapter";
import * as vscode from "vscode";
import { vscode as vscodeAdapter } from "../vscodeAdapter";
import { Options } from "../omnisharp/options";
import ShowInformationMessage from "./utils/ShowInformationMessage";
import { Observable } from "rxjs";
import Disposable from "../Disposable";
import { filter } from 'rxjs/operators';

function ConfigChangeObservable(optionObservable: Observable<Options>): Observable<Options> {
type OptionsKey = keyof Options;

const omniSharpOptions: ReadonlyArray<OptionsKey> = [
"path",
"useGlobalMono",
"enableMsBuildLoadProjectsOnDemand",
"waitForDebugger",
"loggingLevel",
"enableEditorConfigSupport",
"enableDecompilationSupport"
];

function OmniSharpOptionChangeObservable(optionObservable: Observable<Options>): Observable<Options> {
let options: Options;
return optionObservable.pipe(filter(newOptions => {
let changed = (options && hasChanged(options, newOptions));
options = newOptions;
return changed;
}));
return optionObservable.pipe(
filter(newOptions => {
const changed = options && omniSharpOptions.some(key => options[key] !== newOptions[key]);
options = newOptions;
return changed;
})
);
}

export function ShowOmniSharpConfigChangePrompt(optionObservable: Observable<Options>, vscode: vscode): Disposable {
let subscription = ConfigChangeObservable(optionObservable)
.subscribe(_ => {
let message = "OmniSharp configuration has changed. Would you like to relaunch the OmniSharp server with your changes?";
ShowInformationMessage(vscode, message, { title: "Restart OmniSharp", command: 'o.restart' });
});
export function ShowOmniSharpConfigChangePrompt(optionObservable: Observable<Options>, vscode: vscodeAdapter): Disposable {
const subscription = OmniSharpOptionChangeObservable(optionObservable)
.subscribe(showOmniSharpConfigChangedPrompt);

return new Disposable(subscription);
}

function hasChanged(oldOptions: Options, newOptions: Options): boolean {
return (oldOptions.path != newOptions.path ||
oldOptions.useGlobalMono != newOptions.useGlobalMono ||
oldOptions.enableMsBuildLoadProjectsOnDemand != newOptions.enableMsBuildLoadProjectsOnDemand ||
oldOptions.waitForDebugger != newOptions.waitForDebugger ||
oldOptions.loggingLevel != newOptions.loggingLevel);
}
function showOmniSharpConfigChangedPrompt() {
let message = "OmniSharp configuration has changed. Would you like to relaunch the OmniSharp server with your changes?";
ShowInformationMessage(vscode, message, { title: "Restart OmniSharp", command: 'o.restart' });
}
62 changes: 62 additions & 0 deletions src/omnisharp/decompilationPrompt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import * as vscode from "vscode";
import OptionProvider from "../observers/OptionProvider";

export async function getDecompilationAuthorization(context: vscode.ExtensionContext, optionProvider: OptionProvider) {
// If decompilation is disabled the return false
const options = optionProvider.GetLatestOptions();
if (options.enableDecompilationSupport === false) {
return false;
}

// If the terms have been acknowledged for this workspace then return.
let decompilationAutorized = context.workspaceState.get<boolean | undefined>("decompilationAuthorized");
if (decompilationAutorized !== undefined) {
return decompilationAutorized;
}

const result = await promptToAcceptDecompilationTerms();
decompilationAutorized = result === PromptResult.Yes;

await context.workspaceState.update("decompilationAuthorized", decompilationAutorized);

return decompilationAutorized;
}

enum PromptResult {
Dismissed,
Yes,
No
}

interface PromptItem extends vscode.MessageItem {
result: PromptResult;
}

async function promptToAcceptDecompilationTerms() {
return new Promise<PromptResult>((resolve, reject) => {
const message = `IMPORTANT: C# extension includes decompiling functionality (“Decompiler”) that enables reproducing source code from binary code. By accessing and using the Decompiler, you agree to the terms for the Decompiler below. If you do not agree with these terms, do not access or use the Decompiler.
You acknowledge that binary code and source code might be protected by copyright and trademark laws. Before using the Decompiler on any binary code, you need to first:
(i) confirm that the license terms governing your use of the binary code do not contain a provision which prohibits you from decompiling the software; or
(ii) obtain permission to decompile the binary code from the owner of the software.
Your use of the Decompiler is optional. Microsoft is not responsible and disclaims all liability for your use of the Decompiler that violates any laws or any software license terms which prohibit decompiling of the software.
I agree to all of the foregoing:`;

const messageOptions: vscode.MessageOptions = { modal: true };

const yesItem: PromptItem = { title: 'Yes', result: PromptResult.Yes };
const noItem: PromptItem = { title: 'No', result: PromptResult.No, isCloseAffordance: true };

vscode.window.showWarningMessage(message, messageOptions, noItem, yesItem)
.then(selection => resolve(selection?.result ?? PromptResult.Dismissed));
});
}
5 changes: 3 additions & 2 deletions src/omnisharp/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an

const options = optionProvider.GetLatestOptions();
let omnisharpMonoResolver = new OmniSharpMonoResolver(getMonoVersion);
const server = new OmniSharpServer(vscode, provider, packageJSON, platformInfo, eventStream, optionProvider, extensionPath, omnisharpMonoResolver);
const decompilationAuthorized = context.workspaceState.get<boolean | undefined>("decompilationAuthorized") ?? false;
const server = new OmniSharpServer(vscode, provider, packageJSON, platformInfo, eventStream, optionProvider, extensionPath, omnisharpMonoResolver, decompilationAuthorized);
const advisor = new Advisor(server, optionProvider); // create before server is started
const disposables = new CompositeDisposable();
const languageMiddlewareFeature = new LanguageMiddlewareFeature();
Expand Down Expand Up @@ -102,7 +103,7 @@ export async function activate(context: vscode.ExtensionContext, packageJSON: an
localDisposables = null;
}));

disposables.add(registerCommands(server, platformInfo, eventStream, optionProvider, omnisharpMonoResolver, packageJSON, extensionPath));
disposables.add(registerCommands(context, server, platformInfo, eventStream, optionProvider, omnisharpMonoResolver, packageJSON, extensionPath));

if (!context.workspaceState.get<boolean>('assetPromptDisabled')) {
disposables.add(server.onServerStart(() => {
Expand Down
3 changes: 3 additions & 0 deletions src/omnisharp/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class Options {
public enableMsBuildLoadProjectsOnDemand: boolean,
public enableRoslynAnalyzers: boolean,
public enableEditorConfigSupport: boolean,
public enableDecompilationSupport: boolean,
public razorPluginPath?: string,
public defaultLaunchSolution?: string,
public monoPath?: string,
Expand Down Expand Up @@ -67,6 +68,7 @@ export class Options {

const enableRoslynAnalyzers = omnisharpConfig.get<boolean>('enableRoslynAnalyzers', false);
const enableEditorConfigSupport = omnisharpConfig.get<boolean>('enableEditorConfigSupport', false);
const enableDecompilationSupport = omnisharpConfig.get<boolean>('enableDecompilationSupport', false);

const useFormatting = csharpConfig.get<boolean>('format.enable', true);

Expand Down Expand Up @@ -111,6 +113,7 @@ export class Options {
enableMsBuildLoadProjectsOnDemand,
enableRoslynAnalyzers,
enableEditorConfigSupport,
enableDecompilationSupport,
razorPluginPath,
defaultLaunchSolution,
monoPath,
Expand Down
6 changes: 5 additions & 1 deletion src/omnisharp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class OmniSharpServer {
private updateProjectDebouncer = new Subject<ObservableEvents.ProjectModified>();
private firstUpdateProject: boolean;

constructor(private vscode: vscode, networkSettingsProvider: NetworkSettingsProvider, private packageJSON: any, private platformInfo: PlatformInformation, private eventStream: EventStream, private optionProvider: OptionProvider, private extensionPath: string, private monoResolver: IMonoResolver) {
constructor(private vscode: vscode, networkSettingsProvider: NetworkSettingsProvider, private packageJSON: any, private platformInfo: PlatformInformation, private eventStream: EventStream, private optionProvider: OptionProvider, private extensionPath: string, private monoResolver: IMonoResolver, public decompilationAuthorized: boolean) {
this._requestQueue = new RequestQueueCollection(this.eventStream, 8, request => this._makeRequest(request));
let downloader = new OmnisharpDownloader(networkSettingsProvider, this.eventStream, this.packageJSON, platformInfo, extensionPath);
this._omnisharpManager = new OmnisharpManager(downloader, platformInfo);
Expand Down Expand Up @@ -349,6 +349,10 @@ export class OmniSharpServer {
args.push('FormattingOptions:EnableEditorConfigSupport=true');
}

if (this.decompilationAuthorized && options.enableDecompilationSupport === true) {
args.push('RoslynExtensionsOptions:EnableDecompilationSupport=true');
}

let launchInfo: LaunchInfo;
try {
launchInfo = await this._omnisharpManager.GetOmniSharpLaunchInfo(this.packageJSON.defaults.omniSharp, options.path, serverUrl, latestVersionFileServerPath, installPath, this.extensionPath);
Expand Down
2 changes: 1 addition & 1 deletion test/unitTests/Fakes/FakeOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@
import { Options } from "../../../src/omnisharp/options";

export function getEmptyOptions(): Options {
return new Options("", "", false, "", false, 0, 0, false, false, false, false, false, false, 0, 0, false, false, false, false, undefined, "", "");
return new Options("", "", false, "", false, 0, 0, false, false, false, false, false, false, 0, 0, false, false, false, false, false, undefined, "", "");
}
1 change: 1 addition & 0 deletions test/unitTests/optionStream.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ suite('OptionStream', () => {
options.enableMsBuildLoadProjectsOnDemand.should.equal(false);
options.enableRoslynAnalyzers.should.equal(false);
options.enableEditorConfigSupport.should.equal(false);
options.enableDecompilationSupport.should.equal(false);
expect(options.defaultLaunchSolution).to.be.undefined;
});

Expand Down
1 change: 1 addition & 0 deletions test/unitTests/options.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ suite("Options tests", () => {
options.enableMsBuildLoadProjectsOnDemand.should.equal(false);
options.enableRoslynAnalyzers.should.equal(false);
options.enableEditorConfigSupport.should.equal(false);
options.enableDecompilationSupport.should.equal(false);
expect(options.defaultLaunchSolution).to.be.undefined;
});

Expand Down

0 comments on commit 607af72

Please sign in to comment.