Subject: bug#37910: CC Mode 5.33.2 (C++//l); CC-mode inconsistently indents everything as topmost-intro after a while

From: Eric Scrivner <[email-masked]>
Date: Sun, 10 Nov 2019 09:47:35 -0800

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 <[email-masked]> 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).
>