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 <eric.t.scrivner@gmail.com> 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 <SDL.h>
#include <SDL_opengl.h>

#include <sys/mman.h>
#include <x86intrin.h>

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 <acm@muc.de> 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
            ...... <point>

, and <point> 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).