Fix a lot of memory leaks
Added a bunch of code to free created objects. One notable change was to return the next token from FreeToken() to cut down on boiler plate code that kept track of a "consumed" token. Now all that just happens in FreeToken() and I don't have to worry about that anywhere else. Also added FreeNode() to free all nodes. This wasn't done before at all. Like FreeToken(), it will return the next node in the list to avoid having to track that stuff in the calling context.
This commit is contained in:
parent
c17eaf1ec4
commit
b952ac99ac
11
lexer.c
11
lexer.c
|
@ -128,8 +128,6 @@ NextToken(Lexer* l)
|
||||||
{
|
{
|
||||||
tok = newToken(l, TT_ILLEGAL);
|
tok = newToken(l, TT_ILLEGAL);
|
||||||
}
|
}
|
||||||
//printf("Invalid token: %X\n", l->ch);
|
|
||||||
//return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
readChar(l);
|
readChar(l);
|
||||||
|
@ -146,8 +144,11 @@ newWhitespaceToken(Lexer* l)
|
||||||
tok->type = TT_WHITESPACE;
|
tok->type = TT_WHITESPACE;
|
||||||
|
|
||||||
int position = l->position;
|
int position = l->position;
|
||||||
// grab the char so we can use this funciton for both
|
|
||||||
// spaces and tabs.
|
/*
|
||||||
|
* Grab the char so we can use this funciton for both
|
||||||
|
* spaces and tabs.
|
||||||
|
*/
|
||||||
char ch = l->ch;
|
char ch = l->ch;
|
||||||
while (peekChar(l) == ch){
|
while (peekChar(l) == ch){
|
||||||
readChar(l);
|
readChar(l);
|
||||||
|
@ -277,7 +278,7 @@ newToken(Lexer* l,
|
||||||
{
|
{
|
||||||
Token* tok = malloc(sizeof(Token));
|
Token* tok = malloc(sizeof(Token));
|
||||||
char* nc = malloc(sizeof(char)+1);
|
char* nc = malloc(sizeof(char)+1);
|
||||||
*nc = l->ch;
|
nc[0] = l->ch;
|
||||||
nc[1] = '\0';
|
nc[1] = '\0';
|
||||||
tok->type = tt;
|
tok->type = tt;
|
||||||
tok->literal = nc;
|
tok->literal = nc;
|
||||||
|
|
26
lexer.h
26
lexer.h
|
@ -4,21 +4,6 @@
|
||||||
#ifndef LEXER_H
|
#ifndef LEXER_H
|
||||||
#define LEXER_H
|
#define LEXER_H
|
||||||
|
|
||||||
//typedef enum NodeType {
|
|
||||||
// NT_Root,
|
|
||||||
// NT_Header1,
|
|
||||||
// NT_Header2,
|
|
||||||
// NT_Header3,
|
|
||||||
// NT_ListItem,
|
|
||||||
// NT_OrderedListItem,
|
|
||||||
// NT_Paragraph,
|
|
||||||
// NT_PlainText,
|
|
||||||
// NT_BoldText,
|
|
||||||
// NT_UnderlineText,
|
|
||||||
// NT_InlineCode,
|
|
||||||
// NT_BlockCode,
|
|
||||||
//} NodeType;
|
|
||||||
|
|
||||||
typedef struct Lexer {
|
typedef struct Lexer {
|
||||||
char* rawFile;
|
char* rawFile;
|
||||||
int rawLen;
|
int rawLen;
|
||||||
|
@ -32,19 +17,10 @@ typedef struct Lexer {
|
||||||
|
|
||||||
} Lexer;
|
} Lexer;
|
||||||
|
|
||||||
//typedef struct Node {
|
|
||||||
// NodeType type;
|
|
||||||
// char RawText;
|
|
||||||
// int LineNumber;
|
|
||||||
//
|
|
||||||
// //struct Node **ChildNodes;
|
|
||||||
// void** ChildNodes;
|
|
||||||
// int ChildCount;
|
|
||||||
//} Node;
|
|
||||||
|
|
||||||
Lexer* NewLexer(const char* filename);
|
Lexer* NewLexer(const char* filename);
|
||||||
Token* NextToken(Lexer* l);
|
Token* NextToken(Lexer* l);
|
||||||
void ReadChar(Lexer* l);
|
void ReadChar(Lexer* l);
|
||||||
void Parse(Lexer* l);
|
void Parse(Lexer* l);
|
||||||
|
void FreeLexer(Lexer* l);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
32
main.c
32
main.c
|
@ -52,15 +52,17 @@ main(int argc, const char** argv)
|
||||||
}
|
}
|
||||||
while(tt != TT_EOF);
|
while(tt != TT_EOF);
|
||||||
|
|
||||||
|
FreeLexer(l);
|
||||||
|
|
||||||
writeTokenFile(firstToken);
|
writeTokenFile(firstToken);
|
||||||
Node* node = ParseNodes(firstToken);
|
Node* firstNode = ParseNodes(firstToken);
|
||||||
|
Node* node = firstNode;
|
||||||
|
|
||||||
printf("nodes:\n");
|
printf("nodes:\n");
|
||||||
while(node != NULL)
|
while(node != NULL)
|
||||||
{
|
{
|
||||||
/*PrintNodeType(node->type);*/
|
switch (node->type)
|
||||||
|
{
|
||||||
switch (node->type) {
|
|
||||||
case NT_Header1:
|
case NT_Header1:
|
||||||
case NT_Header2:
|
case NT_Header2:
|
||||||
case NT_Header3:
|
case NT_Header3:
|
||||||
|
@ -115,14 +117,20 @@ main(int argc, const char** argv)
|
||||||
node = node->next;
|
node = node->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
//printf("rawLen: %d position: %d readPosition: %d ch: %c line: %d column: %d\n",
|
node = firstNode;
|
||||||
// l->rawLen,
|
firstNode = NULL;
|
||||||
// l->position,
|
|
||||||
// l->readPosition,
|
while ((node = FreeNode(node)) != NULL);
|
||||||
// l->ch,
|
|
||||||
// l->line,
|
if (node != NULL)
|
||||||
// l->column
|
printf("last node != NULL\n");
|
||||||
//);
|
|
||||||
|
if (firstNode != NULL)
|
||||||
|
{
|
||||||
|
printf("firstNode != NULL\n");
|
||||||
|
printf("%s\n", NodeTypeString(firstNode->type));
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
127
node.c
127
node.c
|
@ -1,5 +1,6 @@
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
#include "node.h"
|
#include "node.h"
|
||||||
|
|
||||||
|
@ -19,10 +20,12 @@ ParseNodes(Token* firstToken)
|
||||||
Token* currentToken = firstToken;
|
Token* currentToken = firstToken;
|
||||||
Node* prevNode = NULL;
|
Node* prevNode = NULL;
|
||||||
|
|
||||||
while (1) {
|
while (currentToken != NULL)
|
||||||
|
{
|
||||||
Node* currentNode = NULL;
|
Node* currentNode = NULL;
|
||||||
|
|
||||||
switch (currentToken->type) {
|
switch (currentToken->type)
|
||||||
|
{
|
||||||
case TT_NEWLINE:
|
case TT_NEWLINE:
|
||||||
case TT_WHITESPACE:
|
case TT_WHITESPACE:
|
||||||
break;
|
break;
|
||||||
|
@ -44,21 +47,17 @@ ParseNodes(Token* firstToken)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentToken->next == NULL) {
|
if (currentToken->type == TT_NEWLINE || currentToken->type == TT_WHITESPACE)
|
||||||
break;
|
currentToken = FreeToken(currentToken);
|
||||||
}
|
|
||||||
|
|
||||||
currentToken = currentToken->next;
|
|
||||||
if (currentNode == NULL)
|
if (currentNode == NULL)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (prevNode != NULL) {
|
if (prevNode != NULL)
|
||||||
prevNode->next = currentNode;
|
prevNode->next = currentNode;
|
||||||
}
|
|
||||||
|
|
||||||
if (firstNode == NULL) {
|
if (firstNode == NULL)
|
||||||
firstNode = currentNode;
|
firstNode = currentNode;
|
||||||
}
|
|
||||||
|
|
||||||
prevNode = currentNode;
|
prevNode = currentNode;
|
||||||
}
|
}
|
||||||
|
@ -70,12 +69,13 @@ Node*
|
||||||
parseHeader(Token** startToken)
|
parseHeader(Token** startToken)
|
||||||
{
|
{
|
||||||
Token* t = *startToken;
|
Token* t = *startToken;
|
||||||
|
|
||||||
// Count the number of TT_HASH tokens
|
// Count the number of TT_HASH tokens
|
||||||
int count = 1;
|
int count = 1;
|
||||||
while (t->next != NULL && t->next->type == TT_HASH)
|
while (t->next != NULL && t->next->type == TT_HASH)
|
||||||
{
|
{
|
||||||
count++;
|
count++;
|
||||||
t = t->next;
|
t = FreeToken(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t->next == NULL)
|
if (t->next == NULL)
|
||||||
|
@ -83,12 +83,11 @@ parseHeader(Token** startToken)
|
||||||
printf("Header missing text");
|
printf("Header missing text");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
t = t->next;
|
|
||||||
|
|
||||||
// Trim leading whitespace
|
// Trim leading whitespace
|
||||||
while (t->next != NULL && t->type == TT_WHITESPACE)
|
while (t->next != NULL && t->type == TT_WHITESPACE)
|
||||||
{
|
{
|
||||||
t = t->next;
|
t = FreeToken(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (t->next == NULL)
|
if (t->next == NULL)
|
||||||
|
@ -99,6 +98,7 @@ parseHeader(Token** startToken)
|
||||||
|
|
||||||
Token* end = t;
|
Token* end = t;
|
||||||
int len = 0;
|
int len = 0;
|
||||||
|
|
||||||
// find header text size
|
// find header text size
|
||||||
while (end->type != TT_NEWLINE && end->type != TT_EOF) {
|
while (end->type != TT_NEWLINE && end->type != TT_EOF) {
|
||||||
len += end->length;
|
len += end->length;
|
||||||
|
@ -110,10 +110,9 @@ parseHeader(Token** startToken)
|
||||||
|
|
||||||
while(t != end) {
|
while(t != end) {
|
||||||
strncat(strbuff, t->literal, t->length);
|
strncat(strbuff, t->literal, t->length);
|
||||||
t = t->next;
|
t = FreeToken(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
*startToken = t;
|
*startToken = t;
|
||||||
|
|
||||||
HeaderNode* retval = malloc(sizeof(HeaderNode));
|
HeaderNode* retval = malloc(sizeof(HeaderNode));
|
||||||
|
@ -144,14 +143,19 @@ parseCodeBlock(Token** startToken)
|
||||||
// find closing ticks
|
// find closing ticks
|
||||||
int tlen = 0; // number of tokens
|
int tlen = 0; // number of tokens
|
||||||
int clen = 0; // number of characters
|
int clen = 0; // number of characters
|
||||||
Token* t = *startToken;
|
|
||||||
t = t->next; // skip past the opening triple backtick
|
// skip past the opening triple backtick
|
||||||
|
*startToken = FreeToken(*startToken);
|
||||||
|
|
||||||
// skip the first newline
|
// skip the first newline
|
||||||
if (t->type == TT_NEWLINE) {
|
while ((*startToken)->type == TT_NEWLINE) {
|
||||||
t = t->next;
|
*startToken = FreeToken(*startToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// assign this after we skip tokens so we don't have to
|
||||||
|
// re-skip them later.
|
||||||
|
Token* t = *startToken;
|
||||||
|
|
||||||
while (t->next != NULL && t->type != TT_TRIPLEBACKTICK) {
|
while (t->next != NULL && t->type != TT_TRIPLEBACKTICK) {
|
||||||
if (t->next->type == TT_EOF) {
|
if (t->next->type == TT_EOF) {
|
||||||
printf("premature EOF");
|
printf("premature EOF");
|
||||||
|
@ -174,20 +178,22 @@ parseCodeBlock(Token** startToken)
|
||||||
char* strbuff = malloc(sizeof(char)*clen+1);
|
char* strbuff = malloc(sizeof(char)*clen+1);
|
||||||
strbuff[0] = '\0';
|
strbuff[0] = '\0';
|
||||||
int i;
|
int i;
|
||||||
t = t->next; // skip past the opening triple backtick
|
|
||||||
|
|
||||||
// skip the first newline
|
|
||||||
if (t->type == TT_NEWLINE) {
|
|
||||||
t = t->next;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(i = 0; i < tlen; i++) {
|
for(i = 0; i < tlen; i++) {
|
||||||
strncat(strbuff, t->literal, t->length);
|
strncat(strbuff, t->literal, t->length);
|
||||||
t = t->next;
|
t = FreeToken(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip past closing triple backtick
|
/*
|
||||||
*startToken = t->next;
|
* Skip past closing triple backtick
|
||||||
|
* This is modifying the *parameter* that was passed in,
|
||||||
|
* so we can return the node and advance the token tree.
|
||||||
|
*/
|
||||||
|
*startToken = FreeToken(t);
|
||||||
|
|
||||||
|
while ((*startToken)->type == TT_NEWLINE || (*startToken)->type == TT_WHITESPACE) {
|
||||||
|
*startToken = FreeToken(*startToken);
|
||||||
|
}
|
||||||
|
|
||||||
CodeBlockNode* ret = malloc(sizeof(CodeBlockNode));
|
CodeBlockNode* ret = malloc(sizeof(CodeBlockNode));
|
||||||
ret->type = NT_BlockCode;
|
ret->type = NT_BlockCode;
|
||||||
|
@ -208,14 +214,11 @@ parseParagraph(Token** startToken)
|
||||||
if (t->type == TT_GT) {
|
if (t->type == TT_GT) {
|
||||||
pnode->ptype = PT_Quote;
|
pnode->ptype = PT_Quote;
|
||||||
// consume TT_GT
|
// consume TT_GT
|
||||||
Token* consumed = t;
|
t = FreeToken(t);
|
||||||
t = t->next;
|
|
||||||
FreeToken(consumed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pnode->content = t;
|
pnode->content = t;
|
||||||
Token* prevToken = NULL;
|
Token* prevToken = NULL;
|
||||||
Token* consumed = NULL;
|
|
||||||
|
|
||||||
while(t != NULL)
|
while(t != NULL)
|
||||||
{
|
{
|
||||||
|
@ -226,10 +229,8 @@ parseParagraph(Token** startToken)
|
||||||
if (t->next->type == TT_WHITESPACE)
|
if (t->next->type == TT_WHITESPACE)
|
||||||
{
|
{
|
||||||
// Consume the newline if the next one is a space.
|
// Consume the newline if the next one is a space.
|
||||||
consumed = t;
|
t = FreeToken(t);
|
||||||
t = t->next;
|
|
||||||
prevToken->next = t;
|
prevToken->next = t;
|
||||||
FreeToken(consumed);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -245,18 +246,14 @@ parseParagraph(Token** startToken)
|
||||||
if (pnode->ptype == PT_Quote) {
|
if (pnode->ptype == PT_Quote) {
|
||||||
if (t->type == TT_GT) {
|
if (t->type == TT_GT) {
|
||||||
// removes TT_GT
|
// removes TT_GT
|
||||||
consumed = t;
|
t = FreeToken(t);
|
||||||
t = t->next;
|
|
||||||
prevToken->next = t;
|
prevToken->next = t;
|
||||||
FreeToken(consumed);
|
|
||||||
|
|
||||||
if (t->next != NULL && t->next->type == TT_WHITESPACE)
|
if (t->next != NULL && t->next->type == TT_WHITESPACE)
|
||||||
{
|
{
|
||||||
// removes TT_WHITESPACE
|
// removes TT_WHITESPACE
|
||||||
consumed = t;
|
t = FreeToken(t);
|
||||||
t = t->next;
|
|
||||||
prevToken->next = t;
|
prevToken->next = t;
|
||||||
FreeToken(consumed);
|
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -276,7 +273,6 @@ parseParagraph(Token** startToken)
|
||||||
}
|
}
|
||||||
} // TT_NEWLINE check
|
} // TT_NEWLINE check
|
||||||
|
|
||||||
//printf("t->literal: %s\n", t->literal);
|
|
||||||
if (prevToken != NULL)
|
if (prevToken != NULL)
|
||||||
prevToken->next = t;
|
prevToken->next = t;
|
||||||
prevToken = t;
|
prevToken = t;
|
||||||
|
@ -296,22 +292,22 @@ paragraphEnd:
|
||||||
{
|
{
|
||||||
if(t->next == NULL)
|
if(t->next == NULL)
|
||||||
{
|
{
|
||||||
|
FreeToken(t);
|
||||||
prevToken->next = NULL;
|
prevToken->next = NULL;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (t->next->type == TT_WHITESPACE)
|
else if (t->next->type == TT_WHITESPACE)
|
||||||
{
|
{
|
||||||
// concatinate the two.
|
/* concatinate the two. */
|
||||||
int len = t->length + t->next->length;
|
int len = t->length + t->next->length;
|
||||||
char* newws = malloc(sizeof(char)*len+1);
|
char* newws = malloc(sizeof(char)*len+1);
|
||||||
newws[0] = '\0';
|
newws[0] = '\0';
|
||||||
strncat(newws, t->literal, t->length);
|
strncat(newws, t->literal, t->length);
|
||||||
strncat(newws, t->next->literal, t->next->length);
|
strncat(newws, t->next->literal, t->next->length);
|
||||||
|
|
||||||
consumed = t;
|
t = FreeToken(t);
|
||||||
t = t->next;
|
|
||||||
prevToken->next = t;
|
prevToken->next = t;
|
||||||
FreeToken(consumed);
|
|
||||||
t->length = len;
|
t->length = len;
|
||||||
free(t->literal);
|
free(t->literal);
|
||||||
t->literal = newws;
|
t->literal = newws;
|
||||||
|
@ -346,8 +342,6 @@ NodeTypeString(NodeType t)
|
||||||
return "NT_InlineCode";
|
return "NT_InlineCode";
|
||||||
case NT_BlockCode:
|
case NT_BlockCode:
|
||||||
return "NT_BlockCode";
|
return "NT_BlockCode";
|
||||||
case NT_BlockQuote:
|
|
||||||
return "NT_BlockQuote";
|
|
||||||
case NT_Bold:
|
case NT_Bold:
|
||||||
return "NT_Bold";
|
return "NT_Bold";
|
||||||
case NT_Underline:
|
case NT_Underline:
|
||||||
|
@ -375,3 +369,40 @@ ParagraphTypeString(ParagraphType t)
|
||||||
}
|
}
|
||||||
return "UNKNOWN";
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Node*
|
||||||
|
FreeNode(Node* node)
|
||||||
|
{
|
||||||
|
Node* next = node->next;
|
||||||
|
switch (node->type)
|
||||||
|
{
|
||||||
|
case NT_Header1:
|
||||||
|
case NT_Header2:
|
||||||
|
case NT_Header3:
|
||||||
|
case NT_Header4:
|
||||||
|
free(((HeaderNode*)node)->rawText);
|
||||||
|
break;
|
||||||
|
case NT_BlockCode:
|
||||||
|
free(((CodeBlockNode*)node)->rawText);
|
||||||
|
break;
|
||||||
|
case NT_Error:
|
||||||
|
free(((ErrorNode*)node)->error);
|
||||||
|
break;
|
||||||
|
case NT_Paragraph:
|
||||||
|
{
|
||||||
|
ParagraphNode* pnode = (ParagraphNode*)node;
|
||||||
|
Token* t = pnode->content;
|
||||||
|
while ((t = FreeToken(t)) != NULL);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case NT_UnorderedList:
|
||||||
|
case NT_OrderedList:
|
||||||
|
case NT_InlineCode:
|
||||||
|
case NT_Bold:
|
||||||
|
case NT_Underline:
|
||||||
|
assert(0 && "//TODO");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
free(node);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
2
node.h
2
node.h
|
@ -19,7 +19,6 @@ typedef enum {
|
||||||
NT_Paragraph,
|
NT_Paragraph,
|
||||||
NT_UnorderedList,
|
NT_UnorderedList,
|
||||||
NT_OrderedList,
|
NT_OrderedList,
|
||||||
NT_BlockQuote,
|
|
||||||
|
|
||||||
// Contained elements (cannot be bare)
|
// Contained elements (cannot be bare)
|
||||||
// text modifiers
|
// text modifiers
|
||||||
|
@ -71,5 +70,6 @@ typedef struct {
|
||||||
Node* ParseNodes(Token* firstToken);
|
Node* ParseNodes(Token* firstToken);
|
||||||
char* NodeTypeString(NodeType t);
|
char* NodeTypeString(NodeType t);
|
||||||
char* ParagraphTypeString(ParagraphType t);
|
char* ParagraphTypeString(ParagraphType t);
|
||||||
|
Node* FreeNode(Node* node);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -12,7 +12,6 @@ pellentesque. *Sit fusce.* At ligula dolor parturient sodales auctor. Egestas.
|
||||||
|
|
||||||
this has some `inline
|
this has some `inline
|
||||||
code` in it.
|
code` in it.
|
||||||
|
|
||||||
> Block Quote thing.
|
> Block Quote thing.
|
||||||
> Dictum pharetra nulla _aliquet tincidunt_ parturient netus gravida rutrum
|
> Dictum pharetra nulla _aliquet tincidunt_ parturient netus gravida rutrum
|
||||||
>
|
>
|
||||||
|
|
11
token.c
11
token.c
|
@ -13,23 +13,26 @@ char* printableOnly(char* input);
|
||||||
char*
|
char*
|
||||||
TokenString(Token* t)
|
TokenString(Token* t)
|
||||||
{
|
{
|
||||||
//char* str = malloc(sizeof(char) * 1000);
|
char* printable = printableOnly(t->literal);
|
||||||
snprintf(stringBuff, 1000, "[%d:%d] Type: %s Literal: '%s'",
|
snprintf(stringBuff, STRING_BUFF_SIZE, "[%d:%d] Type: %s Literal: '%s'",
|
||||||
t->line,
|
t->line,
|
||||||
t->column,
|
t->column,
|
||||||
TokenTypeString(t->type),
|
TokenTypeString(t->type),
|
||||||
printableOnly(t->literal)
|
printable
|
||||||
);
|
);
|
||||||
|
|
||||||
|
free(printable);
|
||||||
return stringBuff;
|
return stringBuff;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
Token*
|
||||||
FreeToken(Token* t)
|
FreeToken(Token* t)
|
||||||
{
|
{
|
||||||
|
Token* next = t->next;
|
||||||
if (t->type != TT_TRIPLEBACKTICK)
|
if (t->type != TT_TRIPLEBACKTICK)
|
||||||
free(t->literal);
|
free(t->literal);
|
||||||
free(t);
|
free(t);
|
||||||
|
return next;
|
||||||
}
|
}
|
||||||
|
|
||||||
char*
|
char*
|
||||||
|
|
Loading…
Reference in New Issue