diff --git a/CMark.hsc b/CMark.hsc
--- a/CMark.hsc
+++ b/CMark.hsc
@@ -26,13 +26,15 @@
 
 import Foreign
 import Foreign.C.Types
-import Foreign.C.String (CString, withCString)
+import Foreign.C.String (CString)
 import qualified System.IO.Unsafe as Unsafe
 import GHC.Generics (Generic)
 import Data.Data (Data)
 import Data.Typeable (Typeable)
-import Data.Text (Text, empty, unpack)
+import Data.Text (Text, empty)
 import qualified Data.Text.Foreign as TF
+import qualified Data.ByteString as B
+import Data.Text.Encoding (encodeUtf8)
 import Control.Applicative ((<$>), (<*>))
 
 #include <cmark.h>
@@ -55,14 +57,14 @@
 -- | Convert CommonMark formatted text to a structured 'Node' tree,
 -- which can be transformed or rendered using Haskell code.
 commonmarkToNode :: [CMarkOption] -> Text -> Node
-commonmarkToNode opts s = io $ do
+commonmarkToNode opts s = Unsafe.unsafePerformIO $ do
   nptr <- TF.withCStringLen s $! \(ptr, len) ->
              c_cmark_parse_document ptr len (combineOptions opts)
   fptr <- newForeignPtr c_cmark_node_free nptr
   withForeignPtr fptr toNode
 
 nodeToCommonmark :: [CMarkOption] -> Int -> Node -> Text
-nodeToCommonmark opts width node = io $ do
+nodeToCommonmark opts width node = Unsafe.unsafePerformIO $ do
   nptr <- fromNode node
   fptr <- newForeignPtr c_cmark_node_free nptr
   withForeignPtr fptr $ \ptr -> do
@@ -73,14 +75,15 @@
               -> [CMarkOption]
               -> Text
               -> Text
-commonmarkToX renderer opts s = io $ TF.withCStringLen s $ \(ptr, len) -> do
-  let opts' = combineOptions opts
-  nptr <- c_cmark_parse_document ptr len opts'
-  fptr <- newForeignPtr c_cmark_node_free nptr
-  withForeignPtr fptr $ \p -> do
-    str <- renderer p opts'
-    t <- TF.peekCStringLen $! (str, c_strlen str)
-    return t
+commonmarkToX renderer opts s = Unsafe.unsafePerformIO $
+  TF.withCStringLen s $ \(ptr, len) -> do
+    let opts' = combineOptions opts
+    nptr <- c_cmark_parse_document ptr len opts'
+    fptr <- newForeignPtr c_cmark_node_free nptr
+    withForeignPtr fptr $ \p -> do
+      str <- renderer p opts'
+      t <- TF.peekCStringLen $! (str, c_strlen str)
+      return t
 
 type NodePtr = Ptr ()
 
@@ -263,12 +266,12 @@
             BLOCK_QUOTE -> c_cmark_node_new (#const CMARK_NODE_BLOCK_QUOTE)
             HTML literal -> do
                      n <- c_cmark_node_new (#const CMARK_NODE_HTML)
-                     c_cmark_node_set_literal n (fromtext literal)
+                     c_cmark_node_set_literal n =<< fromtext literal
                      return n
             CODE_BLOCK info literal -> do
                      n <- c_cmark_node_new (#const CMARK_NODE_CODE_BLOCK)
-                     c_cmark_node_set_literal n (fromtext literal)
-                     c_cmark_node_set_fence_info n (fromtext info)
+                     c_cmark_node_set_literal n =<< fromtext literal
+                     c_cmark_node_set_fence_info n =<< fromtext info
                      return n
             LIST attr   -> do
                      n <- c_cmark_node_new (#const CMARK_NODE_LIST)
@@ -290,41 +293,38 @@
             STRONG      -> c_cmark_node_new (#const CMARK_NODE_STRONG)
             LINK url title -> do
                      n <- c_cmark_node_new (#const CMARK_NODE_LINK)
-                     c_cmark_node_set_url n (fromtext url)
-                     c_cmark_node_set_title n (fromtext title)
+                     c_cmark_node_set_url n =<< fromtext url
+                     c_cmark_node_set_title n =<< fromtext title
                      return n
             IMAGE url title -> do
                      n <- c_cmark_node_new (#const CMARK_NODE_IMAGE)
-                     c_cmark_node_set_url n (fromtext url)
-                     c_cmark_node_set_title n (fromtext title)
+                     c_cmark_node_set_url n =<< fromtext url
+                     c_cmark_node_set_title n =<< fromtext title
                      return n
             TEXT literal -> do
                      n <- c_cmark_node_new (#const CMARK_NODE_TEXT)
-                     c_cmark_node_set_literal n (fromtext literal)
+                     c_cmark_node_set_literal n =<< fromtext literal
                      return n
             CODE literal -> do
                      n <- c_cmark_node_new (#const CMARK_NODE_CODE)
-                     c_cmark_node_set_literal n (fromtext literal)
+                     c_cmark_node_set_literal n =<< fromtext literal
                      return n
             INLINE_HTML literal -> do
                      n <- c_cmark_node_new (#const CMARK_NODE_INLINE_HTML)
-                     c_cmark_node_set_literal n (fromtext literal)
+                     c_cmark_node_set_literal n =<< fromtext literal
                      return n
             SOFTBREAK   -> c_cmark_node_new (#const CMARK_NODE_SOFTBREAK)
             LINEBREAK   -> c_cmark_node_new (#const CMARK_NODE_LINEBREAK)
   mapM_ (\child -> fromNode child >>= c_cmark_node_append_child node) children
   return node
 
-io :: IO a -> a
-io = Unsafe.unsafePerformIO
-
 totext :: CString -> IO Text
 totext str
   | str == nullPtr = return empty
   | otherwise      = TF.peekCStringLen (str, c_strlen str)
 
-fromtext :: Text -> CString
-fromtext t = io $! withCString (unpack t) return
+fromtext :: Text -> IO CString
+fromtext t = B.useAsCString (encodeUtf8 t) return
 
 foreign import ccall "string.h strlen"
     c_strlen :: CString -> Int
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -3,8 +3,8 @@
 
 This package provides Haskell bindings for [libcmark], the reference
 parser for [CommonMark], a fully specified variant of Markdown.
-It includes sources for [libcmark] (as of commit f40e098, implementing
-version 0.18 of the spec) and does not require prior installation of
+It includes sources for [libcmark] (version 0.19.0, implementing
+version 0.19 of the spec) and does not require prior installation of
 the C library.
 
 cmark provides the following advantages over existing Markdown
diff --git a/cbits/cmark_version.h b/cbits/cmark_version.h
--- a/cbits/cmark_version.h
+++ b/cbits/cmark_version.h
@@ -1,7 +1,7 @@
 #ifndef CMARK_VERSION_H
 #define CMARK_VERSION_H
 
-#define CMARK_VERSION ((0 << 16) | (18 << 8)  | 1)
-#define CMARK_VERSION_STRING "0.18.1"
+#define CMARK_VERSION ((0 << 16) | (19 << 8)  | 0)
+#define CMARK_VERSION_STRING "0.19.0"
 
 #endif
diff --git a/cbits/commonmark.c b/cbits/commonmark.c
--- a/cbits/commonmark.c
+++ b/cbits/commonmark.c
@@ -49,35 +49,35 @@
 
 static inline bool
 needs_escaping(escaping escape,
-	       int32_t c,
-	       unsigned char next_c,
-	       struct render_state *state)
+               int32_t c,
+               unsigned char next_c,
+               struct render_state *state)
 {
 	if (escape == NORMAL) {
 		return (c == '*' || c == '_' || c == '[' || c == ']' ||
-			c == '<' || c == '>' || c == '\\' || c == '`' ||
-			(c == '&' && isalpha(next_c)) ||
-			(c == '!' && next_c == '[') ||
-			(state->begin_line &&
-			 (c == '-' || c == '+' || c == '#' || c == '=')) ||
-			(c == '#' && (isspace(next_c) || next_c == '\0')) ||
-			((c == '.' || c == ')') &&
-			 isdigit(state->buffer->ptr[state->buffer->size - 1])));
+		        c == '<' || c == '>' || c == '\\' || c == '`' ||
+		        (c == '&' && isalpha(next_c)) ||
+		        (c == '!' && next_c == '[') ||
+		        (state->begin_line &&
+		         (c == '-' || c == '+' || c == '#' || c == '=')) ||
+		        (c == '#' && (isspace(next_c) || next_c == '\0')) ||
+		        ((c == '.' || c == ')') &&
+		         isdigit(state->buffer->ptr[state->buffer->size - 1])));
 	} else if (escape == TITLE) {
 		return (c == '`' || c == '<' || c == '>' || c == '"' ||
-			c == '\\');
+		        c == '\\');
 	} else if (escape == URL) {
 		return (c == '`' || c == '<' || c == '>' || isspace(c) ||
-			c == '\\' || c == ')' || c == '(');
+		        c == '\\' || c == ')' || c == '(');
 	} else {
 		return false;
 	}
 }
 
 static inline void out(struct render_state *state,
-		       cmark_chunk str,
-		       bool wrap,
-		       escaping escape)
+                       cmark_chunk str,
+                       bool wrap,
+                       escaping escape)
 {
 	unsigned char* source = str.data;
 	int length = str.len;
@@ -100,7 +100,7 @@
 			cmark_strbuf_putc(state->buffer, '\n');
 			if (state->need_cr > 1) {
 				cmark_strbuf_put(state->buffer, state->prefix->ptr,
-						 state->prefix->size);
+				                 state->prefix->size);
 			}
 		}
 		state->column = 0;
@@ -111,12 +111,15 @@
 	while (i < length) {
 		if (state->begin_line) {
 			cmark_strbuf_put(state->buffer, state->prefix->ptr,
-					 state->prefix->size);
+			                 state->prefix->size);
 			// note: this assumes prefix is ascii:
 			state->column = state->prefix->size;
 		}
 
 		len = utf8proc_iterate(source + i, length - i, &c);
+		if (len == -1) { // error condition
+			return;  // return without rendering rest of string
+		}
 		nextc = source[i + len];
 		if (c == 32 && wrap) {
 			if (!state->begin_line) {
@@ -124,7 +127,7 @@
 				state->column += 1;
 				state->begin_line = false;
 				state->last_breakable = state->buffer->size -
-					1;
+				                        1;
 				// skip following spaces
 				while (source[i + 1] == ' ') {
 					i++;
@@ -167,7 +170,7 @@
 			// add newline, prefix, and remainder
 			cmark_strbuf_putc(state->buffer, '\n');
 			cmark_strbuf_put(state->buffer, state->prefix->ptr,
-					 state->prefix->size);
+			                 state->prefix->size);
 			cmark_strbuf_put(state->buffer, remainder.data, remainder.len);
 			state->column = state->prefix->size + remainder.len;
 			cmark_chunk_free(&remainder);
@@ -254,8 +257,8 @@
 	}
 	cmark_consolidate_text_nodes(node);
 	return (strncmp(url,
-			(char*)node->as.literal.data,
-			node->as.literal.len) == 0);
+	                (char*)node->as.literal.data,
+	                node->as.literal.len) == 0);
 }
 
 // if node is a block node, returns node.
@@ -265,7 +268,7 @@
 {
 	while (node &&
 	       (node->type < CMARK_NODE_FIRST_BLOCK ||
-		node->type > CMARK_NODE_LAST_BLOCK)) {
+	        node->type > CMARK_NODE_LAST_BLOCK)) {
 		node = node->parent;
 	}
 	return node;
@@ -284,7 +287,7 @@
 	bool entering = (ev_type == CMARK_EVENT_ENTER);
 	const char *info;
 	const char *title;
-	char listmarker[64];
+	cmark_strbuf listmarker = GH_BUF_INIT;
 	char *emph_delim;
 	int marker_width;
 
@@ -293,14 +296,14 @@
 	// a following list.
 	if (!(node->type == CMARK_NODE_ITEM && node->prev == NULL &&
 	      entering)) {
-			tmp = get_containing_block(node);
-			state->in_tight_list_item =
-				(tmp->type == CMARK_NODE_ITEM &&
-				 cmark_node_get_list_tight(tmp->parent)) ||
-				(tmp &&
-				 tmp->parent &&
-				 tmp->parent->type == CMARK_NODE_ITEM &&
-				 cmark_node_get_list_tight(tmp->parent->parent));
+		tmp = get_containing_block(node);
+		state->in_tight_list_item =
+		    (tmp->type == CMARK_NODE_ITEM &&
+		     cmark_node_get_list_tight(tmp->parent)) ||
+		    (tmp &&
+		     tmp->parent &&
+		     tmp->parent->type == CMARK_NODE_ITEM &&
+		     cmark_node_get_list_tight(tmp->parent->parent));
 	}
 
 	switch (node->type) {
@@ -316,7 +319,7 @@
 			cmark_strbuf_puts(state->prefix, "> ");
 		} else {
 			cmark_strbuf_truncate(state->prefix,
-					      state->prefix->size - 2);
+			                      state->prefix->size - 2);
 			blankline(state);
 		}
 		break;
@@ -347,11 +350,12 @@
 			// we ensure a width of at least 4 so
 			// we get nice transition from single digits
 			// to double
-			snprintf(listmarker, 63, "%d%s%s", list_number,
-				 list_delim == CMARK_PAREN_DELIM ?
-				 ")" : ".",
-				 list_number < 10 ? "  " : " ");
-			marker_width = strlen(listmarker);
+			cmark_strbuf_printf(&listmarker,
+			                    "%d%s%s", list_number,
+			                    list_delim == CMARK_PAREN_DELIM ?
+			                    ")" : ".",
+			                    list_number < 10 ? "  " : " ");
+			marker_width = listmarker.size;
 		}
 		if (entering) {
 			if (cmark_node_get_list_type(node->parent) ==
@@ -359,17 +363,18 @@
 				lit(state, "* ", false);
 				cmark_strbuf_puts(state->prefix, "  ");
 			} else {
-				lit(state, listmarker, false);
-				for (i=marker_width; i--;) {
+				lit(state, (char *)listmarker.ptr, false);
+				for (i = marker_width; i--;) {
 					cmark_strbuf_putc(state->prefix, ' ');
 				}
 			}
 		} else {
 			cmark_strbuf_truncate(state->prefix,
-					      state->prefix->size -
-					      marker_width);
+			                      state->prefix->size -
+			                      marker_width);
 			cr(state);
 		}
+		cmark_strbuf_free(&listmarker);
 		break;
 
 	case CMARK_NODE_HEADER:
@@ -403,7 +408,7 @@
 			cmark_strbuf_puts(state->prefix, "    ");
 			out(state, node->as.code.literal, false, LITERAL);
 			cmark_strbuf_truncate(state->prefix,
-					      state->prefix->size - 4);
+			                      state->prefix->size - 4);
 		} else {
 			numticks = longest_backtick_sequence(code) + 1;
 			if (numticks < 3) {
@@ -512,7 +517,7 @@
 			if (entering) {
 				lit(state, "<", false);
 				if (strncmp(cmark_node_get_url(node),
-					    "mailto:", 7) == 0) {
+				            "mailto:", 7) == 0) {
 					lit(state,
 					    (char *)cmark_node_get_url(node) + 7,
 					    false);
@@ -577,9 +582,10 @@
 	if (CMARK_OPT_HARDBREAKS & options) {
 		width = 0;
 	}
-	struct render_state state =
-		{ options, &commonmark, &prefix, 0, width,
-		  0, 0, true, false, false};
+	struct render_state state = {
+		options, &commonmark, &prefix, 0, width,
+		0, 0, true, false, false
+	};
 	cmark_node *cur;
 	cmark_event_type ev_type;
 	cmark_iter *iter = cmark_iter_new(root);
diff --git a/cbits/inlines.c b/cbits/inlines.c
--- a/cbits/inlines.c
+++ b/cbits/inlines.c
@@ -301,8 +301,10 @@
 	                   !utf8proc_is_space(after_char) &&
 	                   !utf8proc_is_punctuation(after_char));
 	if (c == '_') {
-		*can_open = left_flanking && !right_flanking;
-		*can_close = right_flanking && !left_flanking;
+		*can_open = left_flanking &&
+			(!right_flanking || utf8proc_is_punctuation(before_char));
+		*can_close = right_flanking &&
+			(!left_flanking || utf8proc_is_punctuation(after_char));
 	} else if (c == '\'' || c == '"') {
 		*can_open = left_flanking && !right_flanking;
 		*can_close = right_flanking;
@@ -636,7 +638,7 @@
 	}
 
 	cmark_strbuf_unescape(&buf);
-	return cmark_strbuf_detach(&buf);
+	return buf.size == 0 ? NULL : cmark_strbuf_detach(&buf);
 }
 
 unsigned char *cmark_clean_title(cmark_chunk *title)
@@ -660,7 +662,7 @@
 	}
 
 	cmark_strbuf_unescape(&buf);
-	return cmark_strbuf_detach(&buf);
+	return buf.size == 0 ? NULL : cmark_strbuf_detach(&buf);
 }
 
 // Parse an autolink or HTML tag.
diff --git a/cbits/man.c b/cbits/man.c
--- a/cbits/man.c
+++ b/cbits/man.c
@@ -20,6 +20,9 @@
 
 	while (i < length) {
 		len = utf8proc_iterate(source + i, length - i, &c);
+		if (len == -1) { // error condition
+			return;  // return without rendering anything
+		}
 		switch(c) {
 		case 46:
 			if (beginLine) {
diff --git a/cbits/utf8.c b/cbits/utf8.c
--- a/cbits/utf8.c
+++ b/cbits/utf8.c
@@ -172,8 +172,7 @@
 	case 3:
 		uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) <<  6)
 		     + (str[2] & 0x3F);
-		if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000) ||
-		    (uc >= 0xFDD0 && uc < 0xFDF0)) uc = -1;
+		if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000)) uc = -1;
 		break;
 	case 4:
 		uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12)
@@ -182,7 +181,7 @@
 		break;
 	}
 
-	if (uc < 0 || ((uc & 0xFFFF) >= 0xFFFE))
+	if (uc < 0)
 		return -1;
 
 	*dst = uc;
diff --git a/changelog b/changelog
--- a/changelog
+++ b/changelog
@@ -1,3 +1,11 @@
+cmark 0.3.2 (28 Apr 2015)
+
+  * Updated libcmark C sources to 0.19.0.
+  * Fixes to avoid warning for earlier ghc versions.
+  * Refactored, removing the `io` convenience function.
+  * Removed unnecessary `unsafePerformIO` on `fromtext`.
+  * `fromtext`: use `ByteString.useAsCString` + explicit `encodeUtf8`.
+
 cmark 0.3.1 (29 Mar 2015)
 
   * Added nodeToCommonmark.
diff --git a/cmark.cabal b/cmark.cabal
--- a/cmark.cabal
+++ b/cmark.cabal
@@ -1,14 +1,13 @@
 name:                cmark
-version:             0.3.1
+version:             0.3.2
 synopsis:            Fast, accurate CommonMark (Markdown) parser and renderer
 description:
   This package provides Haskell bindings for
   <https://github.com/jgm/cmark libcmark>, the reference
   parser for <http://commonmark.org CommonMark>, a fully
   specified variant of Markdown. It includes sources for
-  libcmark (as of commit f40e098, implementing version 0.18
-  of the CommonMark spec) and does not require prior
-  installation of the C library.
+  libcmark (0.19.0) and does not require prior installation of the
+  C library.
   .
   cmark provides the following advantages over existing
   Markdown libraries for Haskell:
@@ -118,7 +117,7 @@
 
 library
   exposed-modules:     CMark
-  build-depends:       base >=4.5 && < 4.9, text
+  build-depends:       base >=4.5 && < 4.9, text, bytestring
   if impl(ghc < 7.6)
     build-depends:     ghc-prim >= 0.2
   default-language:    Haskell2010
