| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.netbeans.modules.cpplite.debugger; |
| |
| import java.beans.PropertyChangeEvent; |
| import java.beans.PropertyChangeListener; |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.util.ArrayList; |
| import java.util.EventListener; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.Semaphore; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import org.netbeans.api.annotations.common.NonNull; |
| import org.netbeans.api.debugger.ActionsManager; |
| import org.netbeans.api.debugger.Breakpoint; |
| import org.netbeans.api.debugger.DebuggerEngine; |
| import org.netbeans.api.debugger.DebuggerInfo; |
| import org.netbeans.api.debugger.DebuggerManager; |
| import org.netbeans.api.debugger.DebuggerManagerAdapter; |
| import org.netbeans.modules.cnd.debugger.gdb2.mi.MICommand; |
| import org.netbeans.modules.cnd.debugger.gdb2.mi.MICommandInjector; |
| import org.netbeans.modules.cnd.debugger.gdb2.mi.MIConst; |
| import org.netbeans.modules.cnd.debugger.gdb2.mi.MIProxy; |
| import org.netbeans.modules.cnd.debugger.gdb2.mi.MIRecord; |
| import org.netbeans.modules.cnd.debugger.gdb2.mi.MITList; |
| import org.netbeans.modules.cnd.debugger.gdb2.mi.MIValue; |
| import org.netbeans.modules.cpplite.debugger.breakpoints.CPPLiteBreakpoint; |
| import org.netbeans.modules.nativeexecution.api.ExecutionEnvironmentFactory; |
| import org.netbeans.modules.nativeexecution.api.pty.Pty; |
| import org.netbeans.modules.nativeexecution.api.pty.PtySupport; |
| import org.netbeans.spi.debugger.ContextProvider; |
| import org.netbeans.spi.debugger.DebuggerEngineProvider; |
| import org.netbeans.spi.debugger.SessionProvider; |
| import org.netbeans.spi.debugger.ui.DebuggingView; |
| |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.text.Annotatable; |
| import org.openide.text.Line; |
| import org.openide.util.Exceptions; |
| import org.openide.util.Pair; |
| import org.openide.util.RequestProcessor; |
| |
| /** |
| * C/C++ lite debugger. |
| * |
| * @author Honza |
| */ |
| public final class CPPLiteDebugger { |
| |
| private static final Logger LOGGER = Logger.getLogger(CPPLiteDebugger.class.getName()); |
| |
| private CPPLiteDebuggerConfig configuration; |
| private CPPLiteDebuggerEngineProvider engineProvider; |
| private ContextProvider contextProvider; |
| private Process debuggee; |
| private LiteMIProxy proxy; |
| private volatile Object currentLine; |
| private volatile boolean suspended = false; |
| private final List<StateListener> stateListeners = new CopyOnWriteArrayList<>(); |
| private final BreakpointsHandler breakpointsHandler = new BreakpointsHandler(); |
| |
| private final ThreadsCollector threadsCollector = new ThreadsCollector(this); |
| private volatile CPPThread currentThread; |
| private volatile CPPFrame currentFrame; |
| |
| public CPPLiteDebugger(ContextProvider contextProvider) { |
| this.contextProvider = contextProvider; |
| configuration = contextProvider.lookupFirst(null, CPPLiteDebuggerConfig.class); |
| // init engineProvider |
| engineProvider = (CPPLiteDebuggerEngineProvider) contextProvider.lookupFirst(null, DebuggerEngineProvider.class); |
| } |
| |
| void setDebuggee(Process debuggee) { |
| this.debuggee = debuggee; |
| |
| CPPLiteInjector injector = new CPPLiteInjector(debuggee.getOutputStream()); |
| |
| this.proxy = new LiteMIProxy(injector, "(gdb)", "UTF-8"); |
| |
| new Thread(() -> { |
| try (BufferedReader r = new BufferedReader(new InputStreamReader(debuggee.getInputStream()))) { |
| String line; |
| |
| while ((line = r.readLine()) != null) { |
| proxy.processLine(line); |
| } |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| }).start(); |
| |
| proxy.waitStarted(); |
| |
| breakpointsHandler.init(); |
| |
| proxy.send(new Command("-gdb-set target-async")); |
| //proxy.send(new Command("-gdb-set scheduler-locking on")); |
| proxy.send(new Command("-gdb-set non-stop on")); |
| proxy.send(new Command("-exec-run")); |
| } |
| |
| private static class CPPLiteInjector implements MICommandInjector { |
| |
| private final OutputStream out; |
| |
| public CPPLiteInjector(OutputStream out) { |
| this.out = out; |
| } |
| |
| @Override |
| public synchronized void inject(String data) { // inject must not be called concurrently |
| LOGGER.log(Level.FINE, "CPPLiteInjector.inject({0})", data); |
| try { |
| out.write(data.getBytes()); |
| out.flush(); |
| } catch (IOException ex) { |
| throw new IllegalStateException(ex); |
| } |
| } |
| |
| @Override |
| public void log(String data) { |
| LOGGER.log(Level.FINE, "CPPLiteInjector.log({0})", data); |
| } |
| |
| } |
| |
| MIRecord sendAndGet(String command) throws InterruptedException { |
| return sendAndGet(command, false); |
| } |
| |
| MIRecord sendAndGet(String command, boolean waitForRunning) throws InterruptedException { |
| CountDownLatch done = new CountDownLatch(1); |
| MIRecord[] result = new MIRecord[1]; |
| proxy.send(new Command(command) { |
| @Override |
| protected void onDone(MIRecord record) { |
| result[0] = record; |
| done.countDown(); |
| } |
| @Override |
| protected void onError(MIRecord record) { |
| result[0] = record; |
| done.countDown(); |
| } |
| |
| @Override |
| protected void onExit(MIRecord record) { |
| result[0] = record; |
| done.countDown(); |
| } |
| }, waitForRunning); |
| done.await(); |
| return result[0]; |
| } |
| |
| void send(Command command) { |
| proxy.send(command); |
| } |
| |
| // other methods ........................................................... |
| |
| public boolean isSuspended() { |
| return suspended; |
| } |
| |
| private void setSuspended(boolean suspended, CPPThread thread, CPPFrame frame) { |
| boolean suspendedOld; |
| boolean suspendedNew; |
| CPPThread currentThreadOld; |
| CPPThread currentThreadNew; |
| CPPFrame currentFrameOld; |
| CPPFrame currentFrameNew; |
| synchronized (this) { |
| suspendedNew = suspendedOld = this.suspended; |
| currentThreadNew = currentThreadOld = this.currentThread; |
| currentFrameNew = currentFrameOld = this.currentFrame; |
| if (suspended) { |
| if (currentThreadOld == null || currentThreadOld.getStatus() != CPPThread.Status.SUSPENDED) { |
| currentThreadNew = thread; |
| currentFrameNew = frame; |
| } else if (currentThreadOld == thread) { |
| currentFrameNew = frame; |
| } |
| suspendedNew = true; |
| } else { |
| if (thread == currentThreadOld) { |
| suspendedNew = false; |
| currentFrameNew = null; |
| } |
| } |
| this.suspended = suspendedNew; |
| this.currentThread = currentThreadNew; |
| this.currentFrame = currentFrameNew; |
| } |
| if (suspendedNew != suspendedOld) { |
| for (StateListener sl : stateListeners) { |
| sl.suspended(suspendedNew); |
| } |
| } |
| if (currentThreadNew != currentThreadOld) { |
| for (StateListener sl : stateListeners) { |
| sl.currentThread(currentThreadNew); |
| } |
| } |
| if (currentFrameNew != currentFrameOld) { |
| for (StateListener sl : stateListeners) { |
| sl.currentFrame(currentFrameNew); |
| } |
| } |
| } |
| |
| private void fireFinished() { |
| for (StateListener sl : stateListeners) { |
| sl.finished(); |
| } |
| } |
| |
| public void addStateListener(StateListener sl) { |
| stateListeners.add(sl); |
| } |
| |
| public void removeStateListener(StateListener sl) { |
| stateListeners.remove(sl); |
| } |
| |
| public ThreadsCollector getThreads() { |
| return threadsCollector; |
| } |
| |
| public CPPThread getCurrentThread() { |
| return currentThread; |
| } |
| |
| public CPPFrame getCurrentFrame() { |
| return currentFrame; |
| } |
| |
| void setCurrentStackFrame(CPPFrame cppFrame) { |
| CPPThread currentThreadOld; |
| CPPFrame currentFrameOld; |
| CPPThread currentThreadNew = cppFrame.getThread(); |
| synchronized (this) { |
| currentThreadOld = this.currentThread; |
| currentFrameOld = this.currentFrame; |
| this.currentThread = currentThreadNew; |
| this.currentFrame = cppFrame; |
| } |
| if (currentThreadNew != currentThreadOld) { |
| for (StateListener sl : stateListeners) { |
| sl.currentThread(currentThreadNew); |
| } |
| } |
| if (cppFrame != currentFrameOld) { |
| for (StateListener sl : stateListeners) { |
| sl.currentFrame(cppFrame); |
| } |
| } |
| } |
| |
| void setCurrentThread(CPPThread thread) { |
| CPPThread currentThreadOld; |
| CPPFrame currentFrameOld; |
| CPPFrame currentFrameNew; |
| synchronized (this) { |
| currentThreadOld = this.currentThread; |
| if (currentThreadOld == thread) { |
| return; |
| } |
| this.currentThread = thread; |
| currentFrameOld = this.currentFrame; |
| this.currentFrame = currentFrameNew = thread.getTopFrame(); |
| } |
| if (thread != currentThreadOld) { |
| for (StateListener sl : stateListeners) { |
| sl.currentThread(thread); |
| } |
| } |
| if (currentFrameNew != currentFrameOld) { |
| for (StateListener sl : stateListeners) { |
| sl.currentFrame(currentFrameNew); |
| } |
| } |
| } |
| |
| public Object getCurrentLine () { |
| return currentLine; |
| } |
| |
| private volatile boolean finished = false; // When the debugger has finished. |
| |
| public boolean isFinished() { |
| return finished; |
| } |
| |
| /** |
| * should define callStack based on callStackInternal & action. |
| */ |
| void doStep (Object action) { |
| CPPThread thread = currentThread; |
| String threadId = ""; |
| if (thread != null) { |
| thread.notifyRunning(); |
| threadId = " --thread " + thread.getId(); |
| } |
| if (action == ActionsManager.ACTION_STEP_OVER) { |
| proxy.send(new Command("-exec-next" + threadId)); |
| } else if (action == ActionsManager.ACTION_STEP_INTO) { |
| proxy.send(new Command("-exec-step" + threadId)); |
| } else if (action == ActionsManager.ACTION_STEP_OUT) { |
| proxy.send(new Command("-exec-finish" + threadId)); |
| } |
| } |
| |
| void pause() { |
| proxy.send(new Command("-exec-interrupt --all")); |
| } |
| |
| void resume() { |
| threadsCollector.running("all"); |
| proxy.send(new Command("-exec-continue --all")); |
| } |
| |
| void finish () { |
| LOGGER.fine("CPPLiteDebugger.finish()"); |
| if (finished) { |
| LOGGER.fine("finish(): already finished."); |
| return ; |
| } |
| breakpointsHandler.dispose(); |
| proxy.send(new Command("-gdb-exit")); |
| Utils.unmarkCurrent (); |
| engineProvider.getDestructor().killEngine(); |
| finished = true; |
| fireFinished(); |
| LOGGER.fine("finish() done, build finished."); |
| } |
| |
| |
| DebuggingView.DVSupport getDVSupport() { |
| return contextProvider.lookupFirst(null, DebuggingView.DVSupport.class); |
| } |
| |
| |
| private class LiteMIProxy extends MIProxy { |
| |
| private final CountDownLatch startedLatch = new CountDownLatch(1); |
| private final CountDownLatch runningLatch = new CountDownLatch(1); |
| private final CountDownLatch runningCommandLatch = new CountDownLatch(0); |
| private final Semaphore runningCommandSemaphore = new Semaphore(1); |
| |
| LiteMIProxy(MICommandInjector injector, String prompt, String encoding) { |
| super(injector, prompt, encoding); |
| } |
| |
| @Override |
| protected void prompt() { |
| startedLatch.countDown(); |
| } |
| |
| @Override |
| protected void execAsyncOutput(MIRecord record) { |
| LOGGER.log(Level.FINE, "MIProxy.execAsyncOutput({0})", record); |
| //if (record.token() == 0) { |
| switch (record.cls()) { |
| case "stopped": |
| MITList results = record.results(); |
| String threadId = results.getConstValue("thread-id"); |
| MIValue stoppedThreads = results.valueOf("stopped-threads"); |
| if (stoppedThreads != null) { |
| if (stoppedThreads.isConst()) { |
| threadsCollector.stopped(stoppedThreads.asConst().value()); |
| } else { |
| MITList stoppedThreadsList = stoppedThreads.asList(); |
| int size = stoppedThreadsList.size(); |
| String[] ids = new String[size]; |
| for (int i = 0; i < size; i++) { |
| ids[i] = ((MIConst) stoppedThreadsList.get(i)).value(); |
| } |
| threadsCollector.stopped(ids); |
| } |
| } |
| CPPThread thread = threadsCollector.get(threadId); |
| String reason = results.getConstValue("reason", ""); |
| switch (reason) { |
| case "exited-normally": |
| if ('*' == record.type()) { |
| finish(); |
| } else { |
| threadsCollector.remove(threadId); |
| } |
| break; |
| default: |
| MITList topFrameList = (MITList) results.valueOf("frame"); |
| CPPFrame frame = topFrameList != null ? new CPPFrame(thread, topFrameList) : null; |
| thread.setTopFrame(frame); |
| setSuspended(true, thread, frame); |
| if (frame != null) { |
| Line currentLine = frame.location(); |
| if (currentLine != null) { |
| Annotatable[] lines = new Annotatable[] {currentLine}; |
| CPPLiteDebugger.this.currentLine = lines; |
| Utils.markCurrent(lines); |
| Utils.showLine(lines); |
| } |
| } |
| break; |
| } |
| break; |
| case "running": |
| results = record.results(); |
| threadId = results.getConstValue("thread-id"); |
| thread = threadsCollector.running(threadId); |
| setSuspended(false, thread, null); |
| Utils.unmarkCurrent(); |
| break; |
| default: |
| //unknown class, ignore |
| break; |
| } |
| return; |
| //} |
| //super.execAsyncOutput(record); |
| } |
| |
| @Override |
| protected void notifyAsyncOutput(MIRecord record) { |
| LOGGER.log(Level.FINE, "MIProxy.notifyAsyncOutput({0})", record); |
| if ('=' == record.type()) { |
| switch (record.cls()) { |
| case "thread-created": |
| String id = getThreadId(record); |
| threadsCollector.add(id); |
| break; |
| case "thread-exited": |
| id = getThreadId(record); |
| threadsCollector.remove(id); |
| break; |
| } |
| } |
| super.notifyAsyncOutput(record); |
| } |
| |
| private String getThreadId(MIRecord record) { |
| MITList results = record.results(); |
| String id = results.getConstValue("id"); |
| return id; |
| } |
| |
| @Override |
| protected void statusAsyncOutput(MIRecord record) { |
| LOGGER.log(Level.FINE, "MIProxy.statusAsyncOutput({0})", record); |
| super.statusAsyncOutput(record); |
| } |
| |
| @Override |
| protected void result(MIRecord record) { |
| LOGGER.log(Level.FINE, "MIProxy.result({0})", record); |
| switch (record.cls()) { |
| case "running": |
| runningLatch.countDown(); |
| break; |
| } |
| runningCommandSemaphore.release(); |
| super.result(record); |
| } |
| |
| void send(MICommand cmd, boolean waitForRunning) { |
| if (waitForRunning) { |
| waitRunning(); |
| } |
| send(cmd); |
| } |
| |
| @Override |
| public void send(MICommand cmd) { |
| try { |
| startedLatch.await(); |
| runningCommandSemaphore.acquire(); |
| } catch (InterruptedException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| LOGGER.log(Level.FINE, "MIProxy.send({0})", cmd); |
| super.send(cmd); |
| } |
| |
| @Override |
| public boolean processLine(String line) { |
| LOGGER.log(Level.FINER, "MIProxy.processLine({0})", line); |
| return super.processLine(line); |
| } |
| |
| void waitStarted() { |
| try { |
| startedLatch.await(); |
| } catch (InterruptedException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| |
| void waitRunning() { |
| try { |
| runningLatch.await(); |
| } catch (InterruptedException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| } |
| |
| public interface StateListener extends EventListener { |
| |
| void currentThread(CPPThread thread); |
| |
| void currentFrame(CPPFrame frame); |
| |
| void suspended(boolean suspended); |
| |
| void finished(); |
| |
| } |
| |
| public static @NonNull Pair<DebuggerEngine, Process> startDebugging (CPPLiteDebuggerConfig configuration) throws IOException { |
| DebuggerInfo di = DebuggerInfo.create ( |
| "CPPLiteDebuggerInfo", |
| new Object[] { |
| new SessionProvider () { |
| @Override |
| public String getSessionName () { |
| return configuration.getDisplayName (); |
| } |
| |
| @Override |
| public String getLocationName () { |
| return "localhost"; |
| } |
| |
| @Override |
| public String getTypeID () { |
| return "CPPLiteSession"; |
| } |
| |
| @Override |
| public Object[] getServices () { |
| return new Object[] {}; |
| } |
| }, |
| configuration |
| } |
| ); |
| DebuggerEngine[] es = DebuggerManager.getDebuggerManager (). |
| startDebugging (di); |
| Pty pty = PtySupport.allocate(ExecutionEnvironmentFactory.getLocal()); |
| CPPLiteDebugger debugger = es[0].lookupFirst(null, CPPLiteDebugger.class); |
| List<String> executable = new ArrayList<>(); |
| executable.add("gdb"); |
| executable.add("--interpreter=mi"); |
| executable.add("--tty=" + pty.getSlaveName()); |
| executable.addAll(configuration.getExecutable()); |
| Process debuggee = new ProcessBuilder(executable).directory(configuration.getDirectory()).start(); |
| new RequestProcessor(configuration.getDisplayName() + " (pty deallocator)").post(() -> { |
| try { |
| while (debuggee.isAlive()) { |
| try { |
| debuggee.waitFor(); |
| } catch (InterruptedException ex) { |
| //ignore... |
| } |
| } |
| } finally { |
| try { |
| PtySupport.deallocate(pty); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| } |
| } |
| }); |
| debugger.setDebuggee(debuggee); |
| |
| return Pair.of(es[0], new Process() { |
| @Override |
| public OutputStream getOutputStream() { |
| return pty.getOutputStream(); |
| } |
| |
| @Override |
| public InputStream getInputStream() { |
| return pty.getInputStream(); |
| } |
| |
| @Override |
| public InputStream getErrorStream() { |
| return pty.getErrorStream(); |
| } |
| |
| @Override |
| public int waitFor() throws InterruptedException { |
| return debuggee.waitFor(); |
| } |
| |
| @Override |
| public int exitValue() { |
| return debuggee.exitValue(); |
| } |
| |
| @Override |
| public void destroy() { |
| debuggee.destroy(); |
| } |
| }); |
| } |
| |
| private class BreakpointsHandler extends DebuggerManagerAdapter implements PropertyChangeListener { |
| |
| private final Map<String, CPPLiteBreakpoint> breakpointsById = new ConcurrentHashMap<>(); |
| private final Map<CPPLiteBreakpoint, String> breakpointIds = new ConcurrentHashMap<>(); |
| |
| BreakpointsHandler() { |
| } |
| |
| private void init() { |
| DebuggerManager.getDebuggerManager().addDebuggerListener(DebuggerManager.PROP_BREAKPOINTS, this); |
| for (Breakpoint b : DebuggerManager.getDebuggerManager().getBreakpoints()) { |
| if (b instanceof CPPLiteBreakpoint) { |
| CPPLiteBreakpoint cpplineBreakpoint = (CPPLiteBreakpoint) b; |
| addBreakpoint(cpplineBreakpoint); |
| } |
| } |
| } |
| |
| void dispose() { |
| DebuggerManager.getDebuggerManager().removeDebuggerListener(DebuggerManager.PROP_BREAKPOINTS, this); |
| for (Breakpoint b : DebuggerManager.getDebuggerManager().getBreakpoints()) { |
| if (b instanceof CPPLiteBreakpoint) { |
| b.removePropertyChangeListener(this); |
| } |
| } |
| } |
| |
| @Override |
| public void breakpointAdded(Breakpoint breakpoint) { |
| if (breakpoint instanceof CPPLiteBreakpoint) { |
| addBreakpoint((CPPLiteBreakpoint) breakpoint); |
| } |
| } |
| |
| @Override |
| public void breakpointRemoved(Breakpoint breakpoint) { |
| if (breakpoint instanceof CPPLiteBreakpoint) { |
| removeBreakpoint((CPPLiteBreakpoint) breakpoint); |
| } |
| } |
| |
| @Override |
| public void propertyChange(PropertyChangeEvent evt) { |
| Object source = evt.getSource(); |
| if (source instanceof CPPLiteBreakpoint) { |
| String id = breakpointIds.get((CPPLiteBreakpoint) source); |
| if (id != null) { |
| String propertyName = evt.getPropertyName(); |
| switch (propertyName) { |
| case Breakpoint.PROP_ENABLED: |
| if (Boolean.TRUE.equals(evt.getNewValue())) { |
| proxy.send(new Command("-break-enable " + id)); |
| } else { |
| proxy.send(new Command("-break-disable " + id)); |
| } |
| break; |
| } |
| } |
| } |
| } |
| |
| private void addBreakpoint(CPPLiteBreakpoint breakpoint) { |
| Line l = breakpoint.getLine(); |
| FileObject source = l.getLookup().lookup(FileObject.class); |
| File sourceFile = source != null ? FileUtil.toFile(source) : null; |
| if (sourceFile != null) { |
| String disabled = breakpoint.isEnabled() ? "" : "-d "; |
| Command command = new Command("-break-insert " + disabled + sourceFile.getAbsolutePath() + ":" + (l.getLineNumber() + 1)) { |
| @Override |
| protected void onDone(MIRecord record) { |
| MIValue bkpt = record.results().valueOf("bkpt"); |
| if (bkpt instanceof MITList) { |
| breakpointResolved(breakpoint, (MITList) bkpt); |
| } |
| super.onDone(record); |
| } |
| |
| @Override |
| protected void onError(MIRecord record) { |
| String msg = record.results().getConstValue("msg"); |
| breakpointError(breakpoint, msg); |
| super.onError(record); |
| } |
| }; |
| proxy.send(command); |
| } |
| breakpoint.addPropertyChangeListener(this); |
| } |
| |
| private void removeBreakpoint(CPPLiteBreakpoint breakpoint) { |
| String id = breakpointIds.remove(breakpoint); |
| if (id != null) { |
| breakpoint.removePropertyChangeListener(this); |
| Command command = new Command("-break-delete " + id); |
| proxy.send(command); |
| } |
| } |
| |
| private void breakpointResolved(CPPLiteBreakpoint breakpoint, MITList list) { |
| breakpoint.setCPPValidity(Breakpoint.VALIDITY.VALID, null); |
| String id = list.getConstValue("number"); |
| breakpointsById.put(id, breakpoint); |
| breakpointIds.put(breakpoint, id); |
| } |
| |
| private void breakpointError(CPPLiteBreakpoint breakpoint, String msg) { |
| breakpoint.setCPPValidity(Breakpoint.VALIDITY.INVALID, msg); |
| } |
| } |
| } |