]> OCCT Git - occt.git/commitdiff
0024836: Stack overflow when raising exception in low memory condition
authorabv <abv@opencascade.com>
Thu, 7 Jan 2016 07:02:16 +0000 (10:02 +0300)
committerabv <abv@opencascade.com>
Thu, 21 Jan 2016 12:49:51 +0000 (15:49 +0300)
Standard_OutOfMemory exception is refactored so as to avoid memory allocations (which will likely fail) when it is raised:

- method NewInstance() returns static instance (singleton)
- method Raise() raises copy of that singleton, resetting its message string
- message string is stored as field, not allocated dynamically (thus maximum message length is limited by buffer size)

Class Standard_Failure slightly revised: method Destroy() is merged to destructor, methods Get/SetMessageString() are made virtual.

Add test case for the bug

src/NCollection/NCollection_WinHeapAllocator.cxx
src/QABugs/QABugs_20.cxx
src/Standard/FILES
src/Standard/Standard_Failure.cxx
src/Standard/Standard_Failure.hxx
src/Standard/Standard_Failure.lxx [deleted file]
src/Standard/Standard_MMgrOpt.cxx
src/Standard/Standard_OutOfMemory.cxx [new file with mode: 0644]
src/Standard/Standard_OutOfMemory.hxx
tests/bugs/fclasses/bug24836 [new file with mode: 0644]

index 39628f5cae9a06bc1bc2dd2e9e2a099c3fc49b32..4efabbf3f7741d8cad69a5baa418410883cd54de 100644 (file)
@@ -73,7 +73,7 @@ void* NCollection_WinHeapAllocator::Allocate (const Standard_Size theSize)
   if (aResult == NULL)
   {
     char aBuf[128];
-    Sprintf (aBuf, "Failed to allocate " PRIuPTR " bytes in local dynamic heap", theSize);
+    Sprintf (aBuf, "Failed to allocate %" PRIuPTR " bytes in local dynamic heap", theSize);
     Standard_OutOfMemory::Raise (aBuf);
   }
   return aResult;
index 2a467e70d56de322664baead060d380104b231af..f9b2e2bd414d1ae575d7f2722d29716b866754a8 100644 (file)
@@ -18,6 +18,7 @@
 #include <gp_Ax2.hxx>
 #include <Geom_Circle.hxx>
 #include <Geom_SurfaceOfLinearExtrusion.hxx>
+#include <NCollection_List.hxx>
 #include <TColgp_Array2OfPnt.hxx>
 #include <TColStd_Array2OfReal.hxx>
 #include <TColStd_Array1OfReal.hxx>
@@ -1251,6 +1252,82 @@ static Standard_Integer SurfaceGenOCC26675_1( Draw_Interpretor& theDI,
   return 0;
 }
 
+namespace AllocTest
+{
+  // The test is based of occupying of all available virtual memory.
+  // Obviously it has no sense on 64-bit platforms.
+
+  enum Status
+  {
+    NotApplicable = 0x1,
+    OUMCatchOK    = 0x2,
+    OUMCatchFail  = 0x4
+  };
+
+  template<int> int test()
+  {
+    // non-32-bit implementation
+    return NotApplicable;
+  }
+  
+  template<> int test<4>()
+  {
+    // 32-bit implementation
+    NCollection_List<Standard_Address> aList;
+    const Standard_Integer aBlockSizes[] = {100000, 10000, 10};
+    int aStatus = 0;
+
+    // start populate memory with blocks of large size, then
+    // smaller ones and so on according to content of the array aBlockSizes
+    for (size_t i=0; i < sizeof(aBlockSizes)/sizeof(int); i++)
+    {
+      try
+      {
+        for (;;)
+          aList.Append(Standard::Allocate(aBlockSizes[i]));
+      }
+      catch (Standard_Failure)
+      {
+        aStatus |= OUMCatchOK;
+      }
+      catch (...)
+      {
+        aStatus |= OUMCatchFail;
+        break;
+      }
+    }
+    // release all allocated blocks
+    for (NCollection_List<Standard_Address>::Iterator it(aList); it.More(); it.Next())
+    {
+      Standard::Free(it.Value());
+    }
+    return aStatus;
+  }
+}
+
+//=======================================================================
+//function : OCC24836
+//purpose :
+//=======================================================================
+static Standard_Integer OCC24836 (Draw_Interpretor& theDI, Standard_Integer n, const char** a)
+{
+  if (n != 1)
+  {
+    theDI << "Usage : " << a[0] << "\n";
+    return 1;
+  }
+
+  int aStatus = AllocTest::test<sizeof(size_t)>();
+
+  if (aStatus & AllocTest::NotApplicable)
+    theDI << "This test case is not applicable for 64-bit and higher platforms\n";
+  if (aStatus & AllocTest::OUMCatchOK)
+    theDI << "out-of-memory has been caught: OK\n";
+  if (aStatus & AllocTest::OUMCatchFail)
+    theDI << "Error: out-of-memory is not always caught\n";
+  return 0;
+}
+
 
 //=======================================================================
 //function : OCC27021 
@@ -1346,6 +1423,7 @@ void QABugs::Commands_20(Draw_Interpretor& theCommands) {
   const char *group = "QABugs";
 
   theCommands.Add ("OCC26675_1", "OCC26675_1 result", __FILE__, SurfaceGenOCC26675_1, group);
+  theCommands.Add ("OCC24836", "OCC24836", __FILE__, OCC24836, group);
   theCommands.Add("OCC27021", "OCC27021", __FILE__, OCC27021, group);
 
   return;
index 7b437a03c089512b64317ddf282b6f85b28af729..0169bc9b4325e5f6dff132024004f4b7bafdf2a7 100755 (executable)
@@ -29,7 +29,6 @@ Standard_ExtString.cxx
 Standard_ExtString.hxx
 Standard_Failure.cxx
 Standard_Failure.hxx
-Standard_Failure.lxx
 Standard_GUID.cxx
 Standard_GUID.hxx
 Standard_Handle.hxx
@@ -62,6 +61,7 @@ Standard_NullObject.hxx
 Standard_NullValue.hxx
 Standard_NumericError.hxx
 Standard_OStream.hxx
+Standard_OutOfMemory.cxx
 Standard_OutOfMemory.hxx
 Standard_OutOfRange.hxx
 Standard_Overflow.hxx
index 31be9c34bd5792f994663252fce5fba8e8bf19e4..07ecbcc4e1116c69159e1d26e0f98110e7537da0 100644 (file)
@@ -88,7 +88,7 @@ Standard_Failure::Standard_Failure (const Standard_Failure& theFailure)
   myMessage = copy_message(theFailure.myMessage);
 }
 
-void Standard_Failure::Destroy()
+Standard_Failure::~Standard_Failure()
 {
   deallocate_message(myMessage);
 }
@@ -195,3 +195,13 @@ Handle(Standard_Failure) Standard_Failure::NewInstance(const Standard_CString AS
 {
   return new Standard_Failure(AString)  ;
 }
+
+//=======================================================================
+//function : GetMessageString
+//purpose  : Returns error message
+//=======================================================================
+Standard_CString Standard_Failure::GetMessageString () const
+{
+  return (myMessage ? myMessage+sizeof(Standard_Integer) : "");
+}
+
index b48db08c8ec4e795ae8a44256339821b0344ad91..e3a9bb40f6c1357b7d3a6e27ca0c35084c96804d 100644 (file)
@@ -41,19 +41,18 @@ public:
 
   //! Creates a status object of type "Failure".
   Standard_EXPORT Standard_Failure();
-Standard_EXPORT Standard_Failure (const Standard_Failure& f);
-  
+
+  //! Copy constructor
+  Standard_EXPORT Standard_Failure (const Standard_Failure& f);
 
   //! Creates a status object of type "Failure".
   Standard_EXPORT Standard_Failure(const Standard_CString aString);
-Standard_EXPORT Standard_Failure& operator= (const Standard_Failure& f);
-  
-  Standard_EXPORT void Destroy();
-~Standard_Failure()
-{
-  Destroy();
-}
+
+  //! Assignment operator
+  Standard_EXPORT Standard_Failure& operator= (const Standard_Failure& f);
   
+  //! Destructor
+  Standard_EXPORT ~Standard_Failure();
 
   //! Prints on the stream <s> the exception name followed by
   //! the error message.
@@ -63,16 +62,12 @@ Standard_EXPORT Standard_Failure& operator= (const Standard_Failure& f);
   //! Handle(Standard_Failure)&)"
   //! is implemented. (This operator uses the method Print)
   Standard_EXPORT void Print (Standard_OStream& s) const;
-void operator<< (Standard_OStream& s) const
-{
-  Print(s);
-}
   
   //! Returns error message
-    Standard_CString GetMessageString() const;
+  Standard_EXPORT virtual Standard_CString GetMessageString() const;
   
   //! Sets error message
-  Standard_EXPORT void SetMessageString (const Standard_CString aMessage);
+  Standard_EXPORT virtual void SetMessageString (const Standard_CString aMessage);
   
   Standard_EXPORT void Reraise();
   
@@ -133,11 +128,11 @@ private:
 
 };
 
-
-#include <Standard_Failure.lxx>
-
-
-
-
+inline Standard_OStream& operator << (Standard_OStream& AStream,
+                                      const Handle(Standard_Failure)& AFailure)
+{
+  AFailure->Print(AStream);
+  return AStream;
+}
 
 #endif // _Standard_Failure_HeaderFile
diff --git a/src/Standard/Standard_Failure.lxx b/src/Standard/Standard_Failure.lxx
deleted file mode 100644 (file)
index cbc7f6c..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright (c) 1998-1999 Matra Datavision
-// Copyright (c) 1999-2014 OPEN CASCADE SAS
-//
-// This file is part of Open CASCADE Technology software library.
-//
-// This library is free software; you can redistribute it and/or modify it under
-// the terms of the GNU Lesser General Public License version 2.1 as published
-// by the Free Software Foundation, with special exception defined in the file
-// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
-// distribution for complete text of the license and disclaimer of any warranty.
-//
-// Alternatively, this file may be used under the terms of Open CASCADE
-// commercial license or contractual agreement.
-
-// ------------------------------------------------------------------
-// Print (me; s: in out OStream) returns OStream;
-// ------------------------------------------------------------------
-//++++ void Standard_Failure::Print (Standard_OStream& AStream) const
-//++++ {
-//++++   AStream << ": " << myMessage << endl;
-
-inline Standard_OStream& operator <<(Standard_OStream& AStream,
-                                    const Handle(Standard_Failure)& AFailure)
-{
-  AFailure->Print(AStream);
-  return AStream;
-}
-
-//=======================================================================
-//function : GetMessageString
-//purpose  : Returns error message
-//=======================================================================
-inline Standard_CString Standard_Failure::GetMessageString () const
-{
-  return (myMessage ? myMessage+sizeof(Standard_Integer) : myMessage);
-}
-
index 30b837fbc297104a9bfebdc893dac414813bbf51..4d0470926d787993927e869ac31cb9e919cefa9c 100644 (file)
@@ -132,10 +132,6 @@ extern "C" int getpagesize() ;
 #define GET_USER(block)    (((Standard_Size*)(block)) + BLOCK_SHIFT)
 #define GET_BLOCK(storage) (((Standard_Size*)(storage))-BLOCK_SHIFT)
 
-// create static instance of out-of-memory exception to protect
-// against possible lack of memory for its raising
-static Handle(Standard_OutOfMemory) anOutOfMemError = new Standard_OutOfMemory;
-
 //=======================================================================
 //function : Standard_MMgr
 //purpose  : 
@@ -400,7 +396,7 @@ Standard_Address Standard_MMgrOpt::Allocate(const Standard_Size aSize)
           aBlock = (Standard_Size*)calloc(RoundSizeN+BLOCK_SHIFT, sizeof(Standard_Size));
         // if still not succeeded, raise exception
         if ( ! aBlock )
-          anOutOfMemError->Reraise ("Standard_MMgrOpt::Allocate(): malloc failed");
+          Standard_OutOfMemory::Raise ("Standard_MMgrOpt::Allocate(): malloc failed");
       }
 
       // initialize new block header by its size
@@ -725,7 +721,7 @@ retry:
       if ( Purge(Standard_False) )
         goto retry;
       // if nothing helps, raise exception
-      anOutOfMemError->Reraise (strerror(errcode));
+      Standard_OutOfMemory::Raise (strerror(errcode));
     }
 
     // save actually allocated size into argument
@@ -759,7 +755,7 @@ retry:
       char message[BUFSIZE];
       if ( FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, 0, GetLastError(), 0, message, BUFSIZE-1, 0) <=0 )
         strcpy (message, "Standard_MMgrOpt::AllocMemory() failed to mmap");
-      anOutOfMemError->Reraise (message);
+      Standard_OutOfMemory::Raise (message);
     }
 
     // record map handle in the beginning
@@ -782,7 +778,7 @@ retry:
       if ( Purge(Standard_False) )
         goto retry;
       // if nothing helps, raise exception
-      anOutOfMemError->Reraise ("Standard_MMgrOpt::Allocate(): malloc failed");
+      Standard_OutOfMemory::Raise ("Standard_MMgrOpt::Allocate(): malloc failed");
     }
   }
   // clear whole block if clearing option is set
diff --git a/src/Standard/Standard_OutOfMemory.cxx b/src/Standard/Standard_OutOfMemory.cxx
new file mode 100644 (file)
index 0000000..915a3a1
--- /dev/null
@@ -0,0 +1,102 @@
+// Created on: 2016-01-06
+// Created by: Andrey Betenev
+// Copyright (c) 2016 OPEN CASCADE SAS
+//
+// This file is part of Open CASCADE Technology software library.
+//
+// This library is free software; you can redistribute it and/or modify it under
+// the terms of the GNU Lesser General Public License version 2.1 as published
+// by the Free Software Foundation, with special exception defined in the file
+// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
+// distribution for complete text of the license and disclaimer of any warranty.
+//
+// Alternatively, this file may be used under the terms of Open CASCADE
+// commercial license or contractual agreement.
+
+#include <Standard_OutOfMemory.hxx>
+
+#include <algorithm>
+#include <stdlib.h>
+
+IMPLEMENT_STANDARD_RTTIEXT(Standard_OutOfMemory,Standard_ProgramError)
+
+//=======================================================================
+//function : Standard_OutOfMemory
+//purpose  :
+//=======================================================================
+
+Standard_OutOfMemory::Standard_OutOfMemory(const Standard_CString theMessage)
+{
+  // call explicitly own method (non-virtual call)
+  Standard_OutOfMemory::SetMessageString (theMessage);
+}
+
+//=======================================================================
+//function : GetMessageString
+//purpose  :
+//=======================================================================
+
+Standard_CString Standard_OutOfMemory::GetMessageString() const
+{
+  return myBuffer;
+}
+  
+//=======================================================================
+//function : SetMessageString
+//purpose  :
+//=======================================================================
+
+void Standard_OutOfMemory::SetMessageString (const Standard_CString theMessage)
+{
+  // restrict length of the message by buffer size
+  size_t n = (theMessage ? std::min (strlen (theMessage), sizeof(myBuffer) - 1) : 0);
+
+  // first set line end symbol to be safe in case of concurrent call
+  myBuffer[n] = '\0';
+  if (n > 0)
+    memcpy (myBuffer, theMessage, n);
+}
+
+//=======================================================================
+//function : Raise
+//purpose  :
+//=======================================================================
+
+void Standard_OutOfMemory::Raise(const Standard_CString theMessage)
+{
+  NewInstance(theMessage)->Reraise();
+}
+
+//=======================================================================
+//function : Raise
+//purpose  :
+//=======================================================================
+
+void Standard_OutOfMemory::Raise(Standard_SStream& theMessage)
+{
+  NewInstance(theMessage.str().c_str())->Reraise();
+}
+
+//=======================================================================
+//function : NewInstance
+//purpose  :
+//=======================================================================
+
+// global instance must be allocated at load-time
+static Handle(Standard_OutOfMemory) anOutOfMemInstance = new Standard_OutOfMemory;
+
+Handle(Standard_OutOfMemory) Standard_OutOfMemory::NewInstance(const Standard_CString theMessage)
+{
+  anOutOfMemInstance->SetMessageString (theMessage);
+  return anOutOfMemInstance;
+}
+
+//=======================================================================
+//function : Throw
+//purpose  :
+//=======================================================================
+
+void Standard_OutOfMemory::Throw () const
+{
+  throw *this;
+}
index 9eada6dccf330fa2197383fb7d7155d6128043b2..81ed7b34eb084d6b7e80941af4488d55fd2114ee 100644 (file)
@@ -32,6 +32,48 @@ DEFINE_STANDARD_HANDLE(Standard_OutOfMemory, Standard_ProgramError)
   #define Standard_OutOfMemory_Raise_if(CONDITION, MESSAGE)
 #endif
 
-DEFINE_STANDARD_EXCEPTION(Standard_OutOfMemory, Standard_ProgramError)
+//! Standard_OutOfMemory exception is defined explicitly and not by
+//! macro DEFINE_STANDARD_EXCEPTION, to avoid necessity of dynamic
+//! memory allocations during throwing and stack unwinding:
+//! 
+//! - method NewInstance() returns static instance (singleton)
+//! - method Raise() raises copy of that singleton, resetting 
+//!   its message string
+//! - message string is stored as field, not allocated dynamically
+//!   (storable message length is limited by buffer size)
+//!
+//! The reason is that in out-of-memory condition any memory allocation can 
+//! fail, thus use of operator new for allocation of new exception instance 
+//! is dangerous (can cause recursion until stack overflow, see #24836).
+
+class Standard_OutOfMemory : public Standard_ProgramError
+{
+  void Throw () const Standard_OVERRIDE;
+
+public:
+
+  //! Constructor is kept public for backward compatibility
+  Standard_EXPORT Standard_OutOfMemory(const Standard_CString theMessage = 0);
+
+  //! Returns error message
+  Standard_EXPORT Standard_CString GetMessageString() const Standard_OVERRIDE;
+  
+  //! Sets error message
+  Standard_EXPORT void SetMessageString (const Standard_CString aMessage) Standard_OVERRIDE;
+
+  //! Raises exception with specified message string
+  Standard_EXPORT static void Raise(const Standard_CString theMessage = "");
+
+  //! Raises exception with specified message string
+  Standard_EXPORT static void Raise(Standard_SStream& theMessage);
+
+  //! Returns global instance of exception
+  Standard_EXPORT static Handle(Standard_OutOfMemory) NewInstance(const Standard_CString theMessage = "");
+
+  DEFINE_STANDARD_RTTIEXT(Standard_OutOfMemory,Standard_ProgramError)
+
+protected:
+  char myBuffer[1024];
+};
 
 #endif // _Standard_OutOfMemory_HeaderFile
diff --git a/tests/bugs/fclasses/bug24836 b/tests/bugs/fclasses/bug24836
new file mode 100644 (file)
index 0000000..d8cac9c
--- /dev/null
@@ -0,0 +1,11 @@
+puts "============"
+puts "OCC24836"
+puts "============"
+puts ""
+#######################################################################
+#  Stack overflow when raising exception in low memory condition
+#######################################################################
+
+pload QAcommands
+
+OCC24836