/*
* Copyright 2013-2016 Sergey Ignatov, Alexander Zolotov, Florin Patan
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.goide.dlv;
import com.goide.GoConstants;
import com.goide.GoFileType;
import com.goide.dlv.breakpoint.DlvBreakpointProperties;
import com.goide.dlv.breakpoint.DlvBreakpointType;
import com.goide.dlv.protocol.DlvRequest;
import com.goide.util.GoUtil;
import com.intellij.execution.ExecutionResult;
import com.intellij.execution.ui.ExecutionConsole;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.PlainTextLanguage;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileFactory;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.io.socketConnection.ConnectionStatus;
import com.intellij.xdebugger.XDebugSession;
import com.intellij.xdebugger.XSourcePosition;
import com.intellij.xdebugger.breakpoints.XBreakpoint;
import com.intellij.xdebugger.breakpoints.XBreakpointHandler;
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
import com.intellij.xdebugger.evaluation.XDebuggerEditorsProviderBase;
import com.intellij.xdebugger.frame.XSuspendContext;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.concurrency.Promise;
import org.jetbrains.debugger.DebugProcessImpl;
import org.jetbrains.debugger.Location;
import org.jetbrains.debugger.StepAction;
import org.jetbrains.debugger.Vm;
import org.jetbrains.debugger.connection.VmConnection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.goide.dlv.protocol.DlvApi.*;
import static com.intellij.util.ObjectUtils.assertNotNull;
import static com.intellij.util.ObjectUtils.tryCast;
public final class DlvDebugProcess extends DebugProcessImpl<VmConnection<?>> implements Disposable {
public static final boolean IS_DLV_DISABLED = !GoConstants.AMD64.equals(GoUtil.systemArch());
private final static Logger LOG = Logger.getInstance(DlvDebugProcess.class);
private final AtomicBoolean breakpointsInitiated = new AtomicBoolean();
private final AtomicBoolean connectedListenerAdded = new AtomicBoolean();
private static final Consumer<Throwable> THROWABLE_CONSUMER = LOG::info;
@NotNull
private final Consumer<DebuggerState> myStateConsumer = new Consumer<DebuggerState>() {
@Override
public void consume(@NotNull DebuggerState o) {
if (o.exited) {
stop();
return;
}
XBreakpoint<DlvBreakpointProperties> find = findBreak(o.breakPoint);
send(new DlvRequest.StacktraceGoroutine()).done(locations -> {
DlvSuspendContext context = new DlvSuspendContext(DlvDebugProcess.this, o.currentThread.id, locations, getProcessor());
XDebugSession session = getSession();
if (find == null) {
session.positionReached(context);
}
else {
session.breakpointReached(find, null, context);
}
});
}
@Nullable
private XBreakpoint<DlvBreakpointProperties> findBreak(@Nullable Breakpoint point) {
return point == null ? null : breakpoints.get(point.id);
}
};
@NotNull
private <T> Promise<T> send(@NotNull DlvRequest<T> request) {
return send(request, getProcessor());
}
@NotNull
static <T> Promise<T> send(@NotNull DlvRequest<T> request, @NotNull DlvCommandProcessor processor) {
return processor.send(request).rejected(THROWABLE_CONSUMER);
}
@NotNull
private DlvCommandProcessor getProcessor() {
return assertNotNull(tryCast(getVm(), DlvVm.class)).getCommandProcessor();
}
public DlvDebugProcess(@NotNull XDebugSession session, @NotNull VmConnection<?> connection, @Nullable ExecutionResult er) {
super(session, connection, new MyEditorsProvider(), null, er);
}
@NotNull
@Override
protected XBreakpointHandler<?>[] createBreakpointHandlers() {
return new XBreakpointHandler[]{new MyBreakpointHandler()};
}
@NotNull
@Override
public ExecutionConsole createConsole() {
ExecutionResult executionResult = getExecutionResult();
return executionResult == null ? super.createConsole() : executionResult.getExecutionConsole();
}
@Override
protected boolean isVmStepOutCorrect() {
return false;
}
@Override
public void dispose() {
// todo
}
@Override
public boolean checkCanInitBreakpoints() {
if (getConnection().getState().getStatus() == ConnectionStatus.CONNECTED) {
// breakpointsInitiated could be set in another thread and at this point work (init breakpoints) could be not yet performed
return initBreakpointHandlersAndSetBreakpoints(false);
}
if (connectedListenerAdded.compareAndSet(false, true)) {
getConnection().addListener(status -> {
if (status == ConnectionStatus.CONNECTED) {
initBreakpointHandlersAndSetBreakpoints(true);
}
});
}
return false;
}
private boolean initBreakpointHandlersAndSetBreakpoints(boolean setBreakpoints) {
if (!breakpointsInitiated.compareAndSet(false, true)) return false;
Vm vm = getVm();
assert vm != null : "Vm should be initialized";
if (setBreakpoints) {
doSetBreakpoints();
resume(vm);
}
return true;
}
private void doSetBreakpoints() {
AccessToken token = ReadAction.start();
try {
getSession().initBreakpoints();
}
finally {
token.finish();
}
}
private void command(@NotNull @MagicConstant(stringValues = {NEXT, CONTINUE, HALT, SWITCH_THREAD, STEP}) String name) {
send(new DlvRequest.Command(name)).done(myStateConsumer);
}
@Nullable
@Override
protected Promise<?> continueVm(@NotNull Vm vm, @NotNull StepAction stepAction) {
switch (stepAction) {
case CONTINUE:
command(CONTINUE);
break;
case IN:
command(STEP);
break;
case OVER:
command(NEXT);
break;
case OUT:
// todo
break;
}
return null;
}
@NotNull
@Override
public List<Location> getLocationsForBreakpoint(@NotNull XLineBreakpoint<?> breakpoint) {
return Collections.emptyList();
}
@Override
public void runToPosition(@NotNull XSourcePosition position, @Nullable XSuspendContext context) {
// todo
}
@Override
public void stop() {
if (getVm() != null) {
send(new DlvRequest.Detach(true));
}
getSession().stop();
}
private static class MyEditorsProvider extends XDebuggerEditorsProviderBase {
@NotNull
@Override
public FileType getFileType() {
return GoFileType.INSTANCE;
}
@Override
protected PsiFile createExpressionCodeFragment(@NotNull Project project,
@NotNull String text,
@Nullable PsiElement context,
boolean isPhysical) {
return PsiFileFactory.getInstance(project).createFileFromText("dlv-debug.txt", PlainTextLanguage.INSTANCE, text);
}
}
private static final Key<Integer> ID = Key.create("DLV_BP_ID");
private final Map<Integer, XBreakpoint<DlvBreakpointProperties>> breakpoints = ContainerUtil.newConcurrentMap();
private class MyBreakpointHandler extends XBreakpointHandler<XLineBreakpoint<DlvBreakpointProperties>> {
public MyBreakpointHandler() {
super(DlvBreakpointType.class);
}
@Override
public void registerBreakpoint(@NotNull XLineBreakpoint<DlvBreakpointProperties> breakpoint) {
XSourcePosition breakpointPosition = breakpoint.getSourcePosition();
if (breakpointPosition == null) return;
VirtualFile file = breakpointPosition.getFile();
int line = breakpointPosition.getLine();
send(new DlvRequest.CreateBreakpoint(file.getPath(), line + 1))
.done(b -> {
breakpoint.putUserData(ID, b.id);
breakpoints.put(b.id, breakpoint);
getSession().updateBreakpointPresentation(breakpoint, AllIcons.Debugger.Db_verified_breakpoint, null);
})
.rejected(t -> {
String message = t == null ? null : t.getMessage();
getSession().updateBreakpointPresentation(breakpoint, AllIcons.Debugger.Db_invalid_breakpoint, message);
});
}
@Override
public void unregisterBreakpoint(@NotNull XLineBreakpoint<DlvBreakpointProperties> breakpoint, boolean temporary) {
XSourcePosition breakpointPosition = breakpoint.getSourcePosition();
if (breakpointPosition == null) return;
Integer id = breakpoint.getUserData(ID);
if (id == null) return; // obsolete
breakpoint.putUserData(ID, null);
breakpoints.remove(id);
send(new DlvRequest.ClearBreakpoint(id));
}
}
}