Apologies for the second email here. One other possibly symptom here: This only seems to happen in buffers where I used the c++11 multi-line raw string literals. All other buffers seem to be fine atm. On Sun, Nov 10, 2019 at 9:47 AM Eric Scrivner wrote: > I've applied the patch and run emacs with it. Unfortunately, it doesn't > seem to solve the issue. One the bright side, I believe I had buffers which > can reliably reproduce the issue (at least locally). I've copied one such > file below. The issue can be reproduced by simply going to the end of > `main` and attempting to indent the `SQL_Quit()` statement. At first it > attempts to overindent the line as `statement-cont`, then if I go up and > attempt to indent the `PlatformLog` in the else statement then return to > the SDL_Quit and indent it, it has become `topmost-intro`. Apologies for > the long length of the file, but hopefully it can help with reproduction: > > #include "engine_math.h" > #include "engine_types.h" > #include "platform.h" > > #include > #include > > #include > #include > > const f32 VIEWPORT_WIDTH = 1920; > const f32 VIEWPORT_HEIGHT = 1080; > > > /////////////////////////////////////////////////////////////////////////////// > > typedef struct { > game_input Input; > } sdl2_input_state; > > typedef struct { > i32 ToneHz; > i32 ToneVolume; > i32 SampleIndex; > i32 SamplesPerSecond; > i32 BytesPerSample; > i32 WavePeriod; > f64 SineLocation; > } sdl2_audio_config; > > typedef struct { > u8* Buffer; > i32 Size; > i32 ReadCursor; > i32 WriteCursor; > b32 IsPlaying; > SDL_AudioDeviceID DeviceID; > sdl2_audio_config* AudioConfig; > } sdl2_audio_buffer; > > #if HYPERBOREAN_DEBUG > typedef struct { > v2 Pos; > v3 Color; > } sdl2_debug_colored_vertex; > > typedef struct { > i32 PlayCursor; > i32 WriteCursor; > sdl2_debug_colored_vertex V[2]; > } sdl2_debug_time_marker; > #endif > > > /////////////////////////////////////////////////////////////////////////////// > > global_variable b32 GlobalRunning; > global_variable sdl2_input_state GlobalGameInput; > global_variable sdl2_audio_buffer GlobalAudioBuffer; > > > /////////////////////////////////////////////////////////////////////////////// > > void PlatformLog(const char* const Format, ...) > { > va_list args; > va_start(args, Format); > > SDL_LogMessageV( > SDL_LOG_CATEGORY_APPLICATION, > SDL_LOG_PRIORITY_INFO, > Format, > args > ); > > va_end(args); > } > > b32 PlatformReadEntireFile(const char* path, platform_entire_file* Result) > { > FILE* file = fopen(path, "r"); > > if (file == NULL) { > return false; > } > > fseek(file, 0, SEEK_END); > Result->Size = ftell(file); > fseek(file, 0, SEEK_SET); > > Result->Contents = (u8*)malloc(Result->Size); > fread(Result->Contents, Result->Size, 1, file); > fclose(file); > > return true; > } > > void PlatformFreeEntireFile(platform_entire_file* Result) { > if (Result->Contents) { > free(Result->Contents); > Result->Contents = NULL; > Result->Size = 0; > } > } > > void PlatformAudioLock() { > SDL_LockAudioDevice(GlobalAudioBuffer.DeviceID); > } > > void PlatformAudioUnlock() { > SDL_UnlockAudioDevice(GlobalAudioBuffer.DeviceID); > } > > > /////////////////////////////////////////////////////////////////////////////// > > internal void *SDL2VirtualAlloc(u64 SizeBytes, void *BaseAddress = NULL) { > return mmap(BaseAddress, SizeBytes, PROT_READ | PROT_WRITE, > MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); > } > > > /////////////////////////////////////////////////////////////////////////////// > > internal void SDL2VirtualFree(void *Buffer, u64 SizeBytes) { > munmap(Buffer, SizeBytes); > } > > > /////////////////////////////////////////////////////////////////////////////// > > internal bool SDL2VirtualAllocSucceeded(void *Value) { > return Value != MAP_FAILED; > } > > > /////////////////////////////////////////////////////////////////////////////// > > internal void SDL2FillAudioDeviceBuffer(void *UserData, u8 *DeviceBuffer, > i32 Length) { > Assert(UserData != NULL); > Assert(DeviceBuffer != NULL); > > SDL_memset(DeviceBuffer, 0, Length); > > sdl2_audio_buffer *AudioBuffer = (sdl2_audio_buffer *)UserData; > > // Keep track of two regions. Region1 contains everything from the > current > // ReadCursor up until, potentially, the end of the buffer. Region2 only > // exists if we need to circle back around. It contains all the data > from the > // beginning of the buffer up until sufficient bytes are read to meet > Length. > int Region1Size = Length; > int Region2Size = 0; > if (AudioBuffer->ReadCursor + Length > AudioBuffer->Size) { > // Handle looping back from the beginning. > Region1Size = AudioBuffer->Size - AudioBuffer->ReadCursor; > Region2Size = Length - Region1Size; > } > > SDL_memcpy(DeviceBuffer, (AudioBuffer->Buffer + AudioBuffer->ReadCursor), > Region1Size); > SDL_memcpy(&DeviceBuffer[Region1Size], AudioBuffer->Buffer, Region2Size); > > AudioBuffer->ReadCursor = > (AudioBuffer->ReadCursor + Length) % AudioBuffer->Size; > } > > > /////////////////////////////////////////////////////////////////////////////// > > internal void SDL2InitializeAudio(sdl2_audio_buffer *AudioBuffer) { > AudioBuffer->Size = AudioBuffer->AudioConfig->SamplesPerSecond * > AudioBuffer->AudioConfig->BytesPerSample; > AudioBuffer->WriteCursor = AudioBuffer->AudioConfig->BytesPerSample; > AudioBuffer->ReadCursor = 0; > AudioBuffer->Buffer = (u8 *)SDL2VirtualAlloc(AudioBuffer->Size); > Assert(SDL2VirtualAllocSucceeded(AudioBuffer->Buffer)); > > SDL_AudioSpec AudioSettings = {}; > AudioSettings.freq = AudioBuffer->AudioConfig->SamplesPerSecond; > AudioSettings.format = AUDIO_S16; > AudioSettings.channels = 2; > // NOTE: Reduce the number of samples here to decrease audio lag by > causing > // SDL to request audio more frequently. > AudioSettings.samples = 256; > AudioSettings.callback = &SDL2FillAudioDeviceBuffer; > AudioSettings.userdata = (void *)AudioBuffer; > > SDL_AudioSpec ObtainedSettings = {}; > AudioBuffer->DeviceID = > SDL_OpenAudioDevice(NULL, 0, &AudioSettings, &ObtainedSettings, 0); > > if (AudioSettings.format != ObtainedSettings.format) { > SDL_Log("Could not open audio device: %s", SDL_GetError()); > exit(1); > } > } > > > /////////////////////////////////////////////////////////////////////////////// > > internal void SDL2Cleanup(sdl2_audio_buffer *AudioBuffer, > sdl2_input_state *InputState) { > if (AudioBuffer->Buffer) { > SDL2VirtualFree(AudioBuffer->Buffer, AudioBuffer->Size); > } > > SDL_CloseAudioDevice(AudioBuffer->DeviceID); > } > > > /////////////////////////////////////////////////////////////////////////////// > > internal void SDL2ProcessKeyDown(game_button_state *State, b32 IsDown) { > Assert(IsDown != State->EndedDown); > State->EndedDown = IsDown; > ++State->HalfTransitionCount; > } > > > /////////////////////////////////////////////////////////////////////////////// > > #if HYPERBOREAN_DEBUG > internal u32 SDL2DebugVAO; > internal u32 SDL2DebugVBO; > > internal void SDL2DrawSoundBufferMarker(sdl2_audio_buffer *AudioBuffer, > f32 C, > f32 PadX, f32 Top, f32 Bottom, > i32 Value, > sdl2_debug_colored_vertex > *Vertices, > v3 Color, u32 Shader) { > Assert(Value < AudioBuffer->Size); > f32 XReal = C * Value + PadX; > Vertices[0].Pos = {{XReal, Top}}; > Vertices[0].Color = Color; > Vertices[1].Pos = {{XReal, Bottom}}; > Vertices[1].Color = Color; > glUseProgram(Shader); > m4x4 Projection = > OrthographicProjection(0, VIEWPORT_WIDTH, 0, VIEWPORT_HEIGHT, -1.0, > 1.0); > GLint ProjectionLoc = glGetUniformLocation(Shader, "Projection"); > glUniformMatrix4fv(ProjectionLoc, 1, GL_FALSE, (float *)Projection.E); > glBindBuffer(GL_ARRAY_BUFFER, SDL2DebugVBO); > glBufferSubData(GL_ARRAY_BUFFER, 0, 2 * > sizeof(sdl2_debug_colored_vertex), > Vertices); > glBindVertexArray(SDL2DebugVAO); > glDrawArrays(GL_LINES, 0, 2); > glBindVertexArray(0); > } > > internal void SDL2DebugSyncDisplay(sdl2_audio_buffer *AudioBuffer, > u32 MarkerCount, > sdl2_debug_time_marker *Markers, > f32 TargetSecondsPerFrame, u32 Shader) { > f32 PadX = 16; > f32 PadY = 16; > > f32 Top = PadY; > f32 Bottom = VIEWPORT_HEIGHT - PadY; > > f32 C = (f32)(VIEWPORT_WIDTH - 2 * PadX) / (f32)AudioBuffer->Size; > for (u32 MarkerIndex = 0; MarkerIndex < MarkerCount; ++MarkerIndex) { > sdl2_debug_time_marker *Marker = &Markers[MarkerIndex]; > > SDL2DrawSoundBufferMarker(AudioBuffer, C, PadX, Top, Bottom, > Marker->PlayCursor, Marker->V, {{1.0, 1.0, > 1.0}}, > Shader); > SDL2DrawSoundBufferMarker(AudioBuffer, C, PadX, Top, Bottom, > Marker->WriteCursor, Marker->V, {{1.0, 1.0, > 0.0}}, > Shader); > } > } > #endif > > > /////////////////////////////////////////////////////////////////////////////// > > internal int > SDL2GetWindowRefreshRate(SDL_Window *Window) > { > SDL_DisplayMode Mode; > int DisplayIndex = SDL_GetWindowDisplayIndex(Window); > // If we can't find the refresh rate, we'll return this: > int DefaultRefreshRate = 60; > if (SDL_GetDesktopDisplayMode(DisplayIndex, &Mode) != 0) > { > return DefaultRefreshRate; > } > if (Mode.refresh_rate == 0) > { > return DefaultRefreshRate; > } > return Mode.refresh_rate; > } > > > /////////////////////////////////////////////////////////////////////////////// > > internal void SDL2HandleEvent(SDL_Event* Event, sdl2_input_state* > InputState) > { > if (Event->type == SDL_QUIT) > { > GlobalRunning = false; > } > else if (Event->type == SDL_KEYDOWN || Event->type == SDL_KEYUP) > { > SDL_Keycode KeyCode = Event->key.keysym.sym; > bool IsDown = Event->key.state == SDL_PRESSED; > bool WasDown = (Event->key.state == SDL_RELEASED || Event->key.repeat > != 0); > > // Eat key repeats > if (IsDown != WasDown) > { > if (KeyCode == SDLK_LEFT) > { > SDL2ProcessKeyDown(&InputState->Input.Controller.MoveLeft, IsDown); > } > else if (KeyCode == SDLK_RIGHT) > { > SDL2ProcessKeyDown(&InputState->Input.Controller.MoveRight, > IsDown); > } > else if (KeyCode == SDLK_UP) > { > SDL2ProcessKeyDown(&InputState->Input.Controller.MoveUp, IsDown); > } > else if (KeyCode == SDLK_DOWN) > { > SDL2ProcessKeyDown(&InputState->Input.Controller.MoveDown, IsDown); > } > else if (KeyCode == SDLK_SPACE) > { > SDL2ProcessKeyDown(&InputState->Input.Controller.ActionDown, > IsDown); > } > else if (KeyCode == SDLK_ESCAPE) > { > SDL2ProcessKeyDown(&InputState->Input.Controller.Back, IsDown); > } > } > } > else if (Event->type == SDL_MOUSEMOTION) > { > InputState->Input.MouseP.X = Event->motion.x; > InputState->Input.MouseP.Y = Event->motion.y; > } > else if (Event->type == SDL_MOUSEBUTTONDOWN || Event->type == > SDL_MOUSEBUTTONUP) > { > bool IsDown = Event->button.state == SDL_PRESSED; > bool WasDown = (Event->button.state == SDL_RELEASED || > Event->button.clicks != 1); > > if (IsDown != WasDown) { > if (Event->button.button == SDL_BUTTON_LEFT) { > SDL2ProcessKeyDown(&InputState->Input.MouseLeft, IsDown); > } > if (Event->button.button == SDL_BUTTON_RIGHT) { > SDL2ProcessKeyDown(&InputState->Input.MouseRight, IsDown); > } > } > } > } > > > /////////////////////////////////////////////////////////////////////////////// > > int main() { > if (SDL_Init(SDL_INIT_EVERYTHING) == 0) > { > SDL_Window* Window = SDL_CreateWindow( > "Hyperborean", > SDL_WINDOWPOS_UNDEFINED, > SDL_WINDOWPOS_UNDEFINED, > VIEWPORT_WIDTH, > VIEWPORT_HEIGHT, > SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI > ); > > if (Window != NULL) > { > SDL_GLContext GLContext = SDL_GL_CreateContext(Window); > > if (GLContext != NULL) > { > GLenum GlewResult = glewInit(); > if (GlewResult == GLEW_OK) > { > PlatformLog("OpenGL Vendor: %s", glGetString(GL_VENDOR)); > PlatformLog("OpenGL Version: %s", glGetString(GL_VERSION)); > PlatformLog("OpenGL Renderer: %s", glGetString(GL_RENDERER)); > > if (SDL_GL_SetSwapInterval(1) != 0) { > PlatformLog("Unable to SetSwapInterval: %s", SDL_GetError()); > } > > game_memory GameMemory = {}; > > #if HYPERBOREAN_INTERNAL > // NOTE: Hard-code the base address so locations remain constant > between runs > void* BaseAddress = (void*)Terabytes(2); > #else > void* BaseAddress = NULL; > #endif > > GameMemory.PermanentStorageSize = Megabytes(512); > GameMemory.TransientStorageSize = Gigabytes((u64)4); > > u64 TotalSize = > GameMemory.PermanentStorageSize + > GameMemory.TransientStorageSize; > GameMemory.PermanentStorage = SDL2VirtualAlloc(TotalSize, > BaseAddress); > Assert(SDL2VirtualAllocSucceeded(GameMemory.PermanentStorage)); > GameMemory.TransientStorage = ( > (u8*)GameMemory.PermanentStorage + > GameMemory.PermanentStorageSize); > > // Initialize keyboard input > GlobalGameInput = {}; > GlobalGameInput.Input.WindowDim.W = VIEWPORT_WIDTH; > GlobalGameInput.Input.WindowDim.H = VIEWPORT_HEIGHT; > GlobalGameInput.Input.Controller.IsConnected = true; > GlobalGameInput.Input.Controller.IsAnalog = true; > > u32 MaxVolume = 3000; > > GlobalAudioBuffer = {}; > sdl2_audio_config AudioConfig = {}; > AudioConfig.ToneHz = 261; > AudioConfig.ToneVolume = 0.10 * MaxVolume; > AudioConfig.SampleIndex = 0; > AudioConfig.SamplesPerSecond = 48000; > AudioConfig.BytesPerSample = 2 * sizeof(i16); > AudioConfig.WavePeriod = AudioConfig.SamplesPerSecond / > AudioConfig.ToneHz; > GlobalAudioBuffer.AudioConfig = &AudioConfig; > > SDL2InitializeAudio(&GlobalAudioBuffer); > > // Get screen refresh rate. > PlatformLog("Refresh Rate: %d Hz\n", > SDL2GetWindowRefreshRate(Window)); > i32 GameUpdateHz = 60; > f32 TargetSecondsPerFrame = 1.0f / (float)GameUpdateHz; > PlatformLog("Target Secs/Frame: %f\n", TargetSecondsPerFrame); > > // TODO(eric): Feed DPI information into our game and use it for > scaling. > f32 DDPI = 0; > f32 HDPI = 0; > f32 VDPI = 0; > if (SDL_GetDisplayDPI(0, &DDPI, &HDPI, &VDPI) != 0) { > PlatformLog("Unable to get display DPI: %s", SDL_GetError()); > } > PlatformLog("Display DPI: %f - %f x %f", DDPI, HDPI, VDPI); > > f32 FPS = 0; > f32 MPF = 0; > > char WindowTitle[256] = {}; > u64 BeginCounter = SDL_GetPerformanceCounter(); > u64 PerfCounterFrequency = SDL_GetPerformanceFrequency(); > u64 BeginCycles = __rdtsc(); > > #if HYPERBOREAN_DEBUG > game_state* State = (game_state*)GameMemory.PermanentStorage; > u32 DebugMarkerShader = CompileShaders( > &State->TransientArena, > R"END( > #version 430 core > layout (location = 0) in vec2 Position; > layout (location = 1) in vec3 Color; > uniform mat4 Projection; > > out vec3 FragmentColor; > > void main() > { > FragmentColor = Color; > gl_Position = vec4(Position, 0.0, 1.0) * Projection; > } > )END", > R"END( > #version 430 core > in vec3 FragmentColor; > > out vec4 ResultingColor; > > void main() > { > ResultingColor = vec4(FragmentColor, 1.0); > } > )END" > ); > u32 DebugTimeMarkerIndex = 0; > sdl2_debug_time_marker DebugTimeMarkers[GameUpdateHz / 2] = {0}; > PlatformLog("Num Markers: %d\n", ArrayCount(DebugTimeMarkers)); > > glGenVertexArrays(1, &SDL2DebugVAO); > glBindVertexArray(SDL2DebugVAO); > > glGenBuffers(1, &SDL2DebugVBO); > glBindBuffer(GL_ARRAY_BUFFER, SDL2DebugVBO); > glBufferData(GL_ARRAY_BUFFER, > 2*sizeof(sdl2_debug_colored_vertex), NULL, GL_DYNAMIC_DRAW); > glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, > sizeof(sdl2_debug_colored_vertex), NULL); > glEnableVertexAttribArray(0); > glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, > sizeof(sdl2_debug_colored_vertex), (void*)sizeof(v2)); > glEnableVertexAttribArray(1); > > glBindBuffer(GL_ARRAY_BUFFER, 0); > glBindVertexArray(0); > #endif > > u32 BeginTimeMs = SDL_GetTicks(); > > GlobalRunning = true; > SDL_Event event; > while (GlobalRunning) { > while (SDL_PollEvent(&event)) { > SDL2HandleEvent(&event, &GlobalGameInput); > } > > game_input GameInput = {}; > memcpy(&GameInput, &GlobalGameInput.Input, sizeof(game_input)); > > game_audio_buffer AudioBuffer = {}; > AudioBuffer.Buffer = GlobalAudioBuffer.Buffer; > AudioBuffer.Size = GlobalAudioBuffer.Size; > AudioBuffer.ReadCursor = GlobalAudioBuffer.ReadCursor; > AudioBuffer.WriteCursor = GlobalAudioBuffer.WriteCursor; > AudioBuffer.ToneVolume = > GlobalAudioBuffer.AudioConfig->ToneVolume; > AudioBuffer.SamplesPerSecond = > GlobalAudioBuffer.AudioConfig->SamplesPerSecond; > AudioBuffer.BytesPerSample = > GlobalAudioBuffer.AudioConfig->BytesPerSample; > > SDL_LockAudioDevice(GlobalAudioBuffer.DeviceID); > u32 EndTimeMs = SDL_GetTicks(); > GameInput.DeltaTimeSecs = (EndTimeMs - BeginTimeMs) / 1000.0f; > GameUpdateAndRender(&GameMemory, &GameInput, &AudioBuffer); > BeginTimeMs = EndTimeMs; > SDL_UnlockAudioDevice(GlobalAudioBuffer.DeviceID); > > // NOTE: Unpause for the first time here to avoid startup lag > due > // to SDL already playing audio from an empty buffer. Unpausing > // here allows audio to begin playing immediately at > application > // start. > if (!GlobalAudioBuffer.IsPlaying) > { > SDL_PauseAudioDevice(GlobalAudioBuffer.DeviceID, 0); > GlobalAudioBuffer.IsPlaying = true; > } > > // NOTE: Copy back out the write cursor state for the circular > audio buffer > GlobalAudioBuffer.WriteCursor = AudioBuffer.WriteCursor; > > for (size_t ButtonIndex = 0; > ButtonIndex < > ArrayCount(GlobalGameInput.Input.Controller.Buttons); > ButtonIndex++) > { > > GlobalGameInput.Input.Controller.Buttons[ButtonIndex].HalfTransitionCount = > 0; > } > > if (GameInput.QuitRequested) > { > GlobalRunning = false; > } > > #if HYPERBOREAN_DEBUG > { > sdl2_debug_time_marker* Marker = > &DebugTimeMarkers[DebugTimeMarkerIndex++]; > if (DebugTimeMarkerIndex >= ArrayCount(DebugTimeMarkers)) { > DebugTimeMarkerIndex = 0; > } > > Marker->PlayCursor = GlobalAudioBuffer.ReadCursor; > Marker->WriteCursor = GlobalAudioBuffer.WriteCursor; > > SDL2DebugSyncDisplay(&GlobalAudioBuffer, > ArrayCount(DebugTimeMarkers), DebugTimeMarkers, TargetSecondsPerFrame, > DebugMarkerShader); > } > #endif > > SDL_GL_SwapWindow(Window); > > u64 EndCounter = SDL_GetPerformanceCounter(); > u64 CounterElapsed = EndCounter - BeginCounter; > > u64 EndCycles = __rdtsc(); > u64 CyclesElapsed = EndCycles - BeginCycles; > FPS = PerfCounterFrequency / (float)CounterElapsed; > f32 SPF = (float)CounterElapsed / PerfCounterFrequency; > MPF = 1000.0f * CounterElapsed / (double)PerfCounterFrequency; > i32 MCPF = SafeTruncateUInt64(CyclesElapsed / (1000 * 1000)); > > BeginCounter = EndCounter; > BeginCycles = EndCycles; > sprintf( > WindowTitle, > "Hyperborean - %0.02f f/s, %0.04f s/f, %0.02f ms/f, %d mc/f", > FPS, SPF, MPF, MCPF > ); > SDL_SetWindowTitle(Window, WindowTitle); > } > > GameCleanup(&GameMemory); > #if HYPERBOREAN_DEBUG > glDeleteBuffers(1, &SDL2DebugVAO); > glDeleteBuffers(1, &SDL2DebugVBO); > glDeleteShader(DebugMarkerShader); > #endif > > SDL2VirtualFree(GameMemory.PermanentStorage, > GameMemory.PermanentStorageSize); > SDL2VirtualFree(GameMemory.TransientStorage, > GameMemory.TransientStorageSize); > > SDL2Cleanup(&GlobalAudioBuffer, &GlobalGameInput); > } > else > { > PlatformLog("Unable to initialize GLEW: %s\n", > glewGetErrorString(GlewResult)); > } > > SDL_GL_DeleteContext(GLContext); > } > else > { > PlatformLog("Failed to create OpenGL context: %s", SDL_GetError()); > } > > SDL_DestroyWindow(Window); > } > else > { > PlatformLog("Failed to create window: %s", SDL_GetError()); > } > > SDL_Quit(); > } > else > { > PlatformLog("Failed to initialize SDL: %s", SDL_GetError()); > } > > return EXIT_SUCCESS; > } > > On Sun, Nov 10, 2019 at 2:48 AM Alan Mackenzie wrote: > >> Hello, Eric. >> >> Now for a top-post. ;-) >> >> I think I may have found the cause of the bug. It may have been caused >> by overwriting lisp list structure, rather than creating new (parallel) >> list structures - a sort of corruption. See below for a fuller >> explanation. This hypothesis is entirely consistent with the observed >> result (spurious topmost-intro's). >> >> Would you please apply the patch below and byte-compile the result. It >> suffices just to compile cc-engine.el. (If you want any help with >> applying the patch or byte compiling, feel free to send me private >> email.) >> >> Then please try out the patched CC Mode for however long it has taken, >> in the past, to see the sort of bug you reported, and then somewhat >> longer. If the bug fails to show itself, we may well have fixed it. >> Please let me know how things are going. >> >> ############## Optional section. An explanation ####################### >> >> One of the caches CC Mode uses, "c-state-cache", keeps track of the >> nested brace structure. It is (re)calculated on calling the lisp >> function c-parse-state. In essence, it records the position of each >> enclosing brace in a list, the most nested first. So if we had the >> following C++ structure: >> >> { // 1 >> ...... >> { // 2 >> ...... >> { // 3 >> ...... >> >> , and was at the marked position, our c-state-cache would be the >> list of the three brace positions (P3 P2 P1). One of its uses is >> determining if some point is at the top level or not. If the >> c-state-cache list for that point is empty, it is at the top level. >> >> As point moves through the buffer, and c-parse-state is called from >> somewhere else, c-state-cache is "altered" in a highly optimised >> fashion. This avoids having to scan large portions of the buffer too >> often, to determine the brace structure. The nature of this "altering" >> is what is causing the problem. >> >> When the buffer is narrowed, say beginning with the brace 2, calling >> c-parse-state now has to return (P3 P2), because the brace 1 is now >> outside the visible portion. >> >> The suspected bug cause is the way (P3 P2 P1) is changed to (P3 P2) on >> this narrowing. Up to now the list structure itself has been changed, >> rather than making a copy of the structure. >> >> So, what may have been happening is that CC Mode is looping through the >> c-state-cache to determine whether point is at top level. (Being >> directly inside a class or namespace, etc., counts as "top level"). If >> point is inside brace 1, the loop will try to determine that P1 is not a >> class, etc., and return "not at top level". However, if c-parse-state >> had been called with the above narrowing, c-state-cache is now (P3 P2), >> point appears to be outside every brace, and the loop spuriously returns >> "at top level". This is what I think has been happening. >> >> When the code wrongly reports "at top level", we get the unwanted >> topmost-intro analyses. >> >> The solution to this is when the buffer is narrowed and we call >> c-parse-state, we make a COPY of the c-state-cache list, leaving the >> original unmolested for its original owner. This is what the patch >> does. >> >> ############## End of optional section. ################################ >> >> Here is the patch. It should work in Emacs-26.3, even though the line >> numbers are now a bit different: >> >> >> >> diff -r 2783baa48d44 cc-engine.el >> --- a/cc-engine.el Fri Oct 25 20:00:14 2019 +0000 >> +++ b/cc-engine.el Sun Nov 10 10:30:17 2019 +0000 >> @@ -3690,7 +3690,13 @@ >> ; brace pair. >> (setq c-state-cache nil >> c-state-cache-good-pos c-state-min-scan-pos) >> - (setcdr ptr nil) >> + ;; Do not alter the original `c-state-cache' structure, since >> there >> + ;; may be a loop suspended which is looping through that >> structure. >> + ;; This may have been the cause of bug #37910. >> + (let ((cdr-ptr (cdr ptr))) >> + (setcdr ptr nil) >> + (setq c-state-cache (copy-sequence c-state-cache)) >> + (setcdr ptr cdr-ptr)) >> (setq c-state-cache-good-pos (1+ (c-state-cache-top-lparen)))) >> ))) >> >> @@ -3793,11 +3799,12 @@ >> (setq new-cons (cons bra (1+ ce))) >> (cond >> ((consp (car c-state-cache)) >> - (setcar c-state-cache new-cons)) >> + (setq c-state-cache (cons new-cons (cdr >> c-state-cache)))) >> ((and (numberp (car c-state-cache)) ; probably never >> happens >> (< ce (car c-state-cache))) >> - (setcdr c-state-cache >> - (cons new-cons (cdr c-state-cache)))) >> + (setq c-state-cache >> + (cons (car c-state-cache) >> + (cons new-cons (cdr c-state-cache))))) >> (t (setq c-state-cache (cons new-cons c-state-cache))))) >> >> ;; We haven't found a brace pair. Record this in the cache. >> @@ -3998,7 +4005,7 @@ >> (when (and c-state-cache >> (consp (car c-state-cache)) >> (> (cdar c-state-cache) upper-lim)) >> - (setcar c-state-cache (caar c-state-cache)) >> + (setq c-state-cache (cons (caar c-state-cache) (cdr >> c-state-cache))) >> (setq scan-back-pos (car c-state-cache) >> cons-separated t)) >> >> @@ -4135,7 +4142,7 @@ >> ;; knowledge of what's inside these braces, we have no alternative >> but >> ;; to direct the caller to scan the buffer from the opening brace. >> (setq pos (caar c-state-cache)) >> - (setcar c-state-cache pos) >> + (setq c-state-cache (cons pos (cdr c-state-cache))) >> (list (1+ pos) pos t)) ; return value. We've just converted a >> brace pair >> ; entry into a { entry, so the caller needs >> to >> ; search for a brace pair before the {. >> >> >> >> >> On Sun, Oct 27, 2019 at 15:39:56 +0000, Alan Mackenzie wrote: >> > Hello, Eric. >> >> > On Thu, Oct 24, 2019 at 12:06:18 -0700, Eric Scrivner wrote: >> > > This seems related to (if not the same as) bug #5490. >> >> > > - This happens randomly and then randomly stops happening (cache >> expiry >> > > maybe?) >> > > - M-x revert-buffer does not fix. >> >> > Thanks for taking the trouble to report this bug, and thanks even more >> > for including so much information. >> >> > > Here's a snippet from the buffer at the time this happen, as you can >> see >> > > there seems to be a region until the end where everything becomes >> > > topmost-intro: >> >> > > } // ((block-close 18328)) >> > > // ((statement 9560)) >> > > SDL_DestroyWindow(Window); // ((statement 9560)) >> > > } // ((block-close 9490)) >> > > else // ((else-clause 9466)) >> > > { // >> > > ((substatement-open 18464)) >> > > PlatformLog("Failed to create window: %s", SDL_GetError()); // >> > > ((statement-block-intro 18473)) >> > > } // ((block-close 18473)) >> > > // ((topmost-intro 18576)) >> > > // ((topmost-intro 18576)) >> > > SDL_Quit(); // ((topmost-intro 18541)) >> > > } // ((topmost-intro 18548)) >> > > else // ((else-clause 9188)) >> > > { // ((substatement-open 18845)) >> > > PlatformLog("Failed to initialize SDL: %s", SDL_GetError()); // >> > > ((statement-block-intro 18901))((statement-block-intro 18724)) >> > > } // >> > > ((block-close 18901)) >> > > // ((topmost-intro 19093)) >> > > return EXIT_SUCCESS; // ((topmost-intro 19093)) >> > > } // ((topmost-intro 19242)) >> >> > At the moment, all I can do is acknowledge receipt of your report. It's >> > obviously not an easy bug to diagnose. >> >> > I can see two ways of making progress: (i) Inspecting >> > c-guess-basic-syntax, the function which analyses code and produces >> > (amongs other things) all these topmost-intro's. It is essentially a >> > large cond form (Lisp's equivalent of a switch), and the single place >> > which produces topmost-intro comes fairly early on in this cond form; >> > (ii) Determine what aspects of a buffer do not get reinitialised after >> > evaluating M-x revert-buffer. That could provide some clue. >> >> > [ CC Mode config dump acknowledged with thanks, but snipped ] >> >> > Just one thing. If you haven't already done so, could you make a backup >> > copy of a buffer which triggers the bug, just in case after some future >> > edit it no longer does so. Thanks! >> >> -- >> Alan Mackenzie (Nuremberg, Germany). >> >