mirror of
https://github.com/OlafvdSpek/ctemplate.git
synced 2025-09-28 19:05:49 +08:00
* BUGFIX: was reloading string tpls in some situations (panicker)
* BUGFIX: fix recounting to avoid accessing freed memory (panicker) * Performance improvements for small_map (wonchun) * PORTING: Avoid SIGBUS on sparc by better aligning memory (csilvers) * Allow lowercase words in pragma contexts (csilvers) * BUGFIX: Fix a C++ conformance bug involving const (chandlerc) * Enable full word matching for tpl filenames (aneeshnaman)
This commit is contained in:
parent
8c338f322e
commit
2f5f4b7baa
|
@ -66,13 +66,13 @@ _START_GOOGLE_NAMESPACE_
|
|||
// map type has a "key_equal" member (hash_map does), then that
|
||||
// will be used by default. Otherwise you must specify this
|
||||
// manually.
|
||||
// MapInitFunctor: A functor that takes a ManualConstructor<NormalMap>*
|
||||
// and uses it to initialize the map. This functor will be
|
||||
// called at most once per small_map, when the map exceeds the
|
||||
// threshold of kArraySize and we are about to copy values from
|
||||
// the array to the map. The functor *must* call one of the Init()
|
||||
// methods provided by ManualConstructor, since after it runs we
|
||||
// assume that the NormalMap has been initialized.
|
||||
// MapInit: A functor that takes a ManualConstructor<NormalMap>* and uses it to
|
||||
// initialize the map. This functor will be called at most once per
|
||||
// small_map, when the map exceeds the threshold of kArraySize and we
|
||||
// are about to copy values from the array to the map. The functor
|
||||
// *must* call one of the Init() methods provided by
|
||||
// ManualConstructor, since after it runs we assume that the NormalMap
|
||||
// has been initialized.
|
||||
//
|
||||
// example:
|
||||
// small_map<hash_map<string, int> > days;
|
||||
|
@ -83,6 +83,9 @@ _START_GOOGLE_NAMESPACE_
|
|||
// days["thursday" ] = 4;
|
||||
// days["friday" ] = 5;
|
||||
// days["saturday" ] = 6;
|
||||
//
|
||||
// You should assume that small_map might invalidate all the iterators
|
||||
// on any call to erase(), insert() and operator[].
|
||||
template <typename NormalMap>
|
||||
class small_map_default_init {
|
||||
public:
|
||||
|
@ -105,7 +108,7 @@ class small_map {
|
|||
|
||||
small_map() : size_(0), functor_(MapInit()) {}
|
||||
|
||||
small_map(const MapInit& functor) : size_(0), functor_(functor) {}
|
||||
explicit small_map(const MapInit& functor) : size_(0), functor_(functor) {}
|
||||
|
||||
// Allow copy-constructor and assignment, since STL allows them too.
|
||||
small_map(const small_map& src) {
|
||||
|
@ -274,6 +277,7 @@ class small_map {
|
|||
return iterator(map()->find(key));
|
||||
}
|
||||
}
|
||||
|
||||
const_iterator find(const key_type& key) const {
|
||||
key_equal compare;
|
||||
if (size_ >= 0) {
|
||||
|
@ -288,11 +292,14 @@ class small_map {
|
|||
}
|
||||
}
|
||||
|
||||
// Invalidates iterators.
|
||||
data_type& operator[](const key_type& key) {
|
||||
key_equal compare;
|
||||
|
||||
if (size_ >= 0) {
|
||||
for (int i = 0; i < size_; i++) {
|
||||
// operator[] searches backwards, favoring recently-added
|
||||
// elements.
|
||||
for (int i = size_-1; i >= 0; --i) {
|
||||
if (compare(array_[i]->first, key)) {
|
||||
return array_[i]->second;
|
||||
}
|
||||
|
@ -309,6 +316,7 @@ class small_map {
|
|||
}
|
||||
}
|
||||
|
||||
// Invalidates iterators.
|
||||
std::pair<iterator, bool> insert(const value_type& x) {
|
||||
key_equal compare;
|
||||
|
||||
|
@ -319,7 +327,7 @@ class small_map {
|
|||
}
|
||||
}
|
||||
if (size_ == kArraySize) {
|
||||
ConvertToRealMap();
|
||||
ConvertToRealMap(); // Invalidates all iterators!
|
||||
std::pair<typename NormalMap::iterator, bool> ret = map_->insert(x);
|
||||
return make_pair(iterator(ret.first), ret.second);
|
||||
} else {
|
||||
|
@ -332,6 +340,7 @@ class small_map {
|
|||
}
|
||||
}
|
||||
|
||||
// Invalidates iterators.
|
||||
template <class InputIterator>
|
||||
void insert(InputIterator f, InputIterator l) {
|
||||
while (f != l) {
|
||||
|
@ -381,16 +390,16 @@ class small_map {
|
|||
size_ = 0;
|
||||
}
|
||||
|
||||
// Invalidates iterators.
|
||||
void erase(const iterator& position) {
|
||||
if (size_ >= 0) {
|
||||
int i = position.array_iter_ - array_;
|
||||
array_[i++].Destroy();
|
||||
// Move the rest of the array back one.
|
||||
for (; i < size_; i++) {
|
||||
array_[i - 1].Init(*array_[i]);
|
||||
array_[i].Destroy();
|
||||
}
|
||||
array_[i].Destroy();
|
||||
--size_;
|
||||
if (i != size_) {
|
||||
array_[i].Init(*array_[size_]);
|
||||
array_[size_].Destroy();
|
||||
}
|
||||
} else {
|
||||
map_->erase(position.hash_iter_);
|
||||
}
|
||||
|
@ -440,6 +449,7 @@ class small_map {
|
|||
|
||||
private:
|
||||
int size_; // negative = using hash_map
|
||||
|
||||
MapInit functor_;
|
||||
|
||||
// We want to call constructors and destructors manually, but we don't
|
||||
|
|
|
@ -52,6 +52,14 @@ bool @ac_windows_dllexport@ IsDirectory(const std::string& path); // ends in "
|
|||
void @ac_windows_dllexport@ NormalizeDirectory(std::string* dir); // appends /
|
||||
std::string @ac_windows_dllexport@ Basename(const std::string& path);
|
||||
|
||||
// Returns true iff text contains the word as a full word, i.e. delimited by one
|
||||
// of [.,_-#*?:] on both the sides.
|
||||
// This is used while loading a template, to check that the file's name matches
|
||||
// the auto-escape mode specified by it.
|
||||
// NOTE: This assumes that the word doesn't contain any of the delimiter
|
||||
// characters.
|
||||
bool @ac_windows_dllexport@ ContainsFullWord(const std::string& text, const std::string& word);
|
||||
|
||||
@ac_google_end_namespace@
|
||||
|
||||
#endif // TEMPLATE_TEMPLATE_PATHOPS_H_
|
||||
|
|
|
@ -445,18 +445,19 @@ static const char *memmatch(const char *haystack, size_t haystack_len,
|
|||
// we ignore it and just rely on the LOG(WARNING) in the logs.
|
||||
static bool FilenameValidForContext(const string& filename,
|
||||
TemplateContext context) {
|
||||
// TODO(jad): Improve by also checking for "word" boundaries.
|
||||
if ( filename.find("css") != string::npos ||
|
||||
filename.find("stylesheet") != string::npos ||
|
||||
filename.find("style") != string::npos) {
|
||||
string stripped_filename = Basename(filename);
|
||||
|
||||
if (ctemplate::ContainsFullWord(stripped_filename, "css") ||
|
||||
ctemplate::ContainsFullWord(stripped_filename, "stylesheet") ||
|
||||
ctemplate::ContainsFullWord(stripped_filename, "style")) {
|
||||
if (context != TC_CSS) {
|
||||
LOG(WARNING) << "Template filename " << filename
|
||||
<< " indicates CSS but given TemplateContext"
|
||||
<< " was not TC_CSS." << endl;
|
||||
return false;
|
||||
}
|
||||
} else if (filename.find("js") != string::npos ||
|
||||
filename.find("javascript") != string::npos) {
|
||||
} else if (ctemplate::ContainsFullWord(stripped_filename, "js") ||
|
||||
ctemplate::ContainsFullWord(stripped_filename, "javascript")) {
|
||||
if (context != TC_JS) {
|
||||
LOG(WARNING) << "Template filename " << filename
|
||||
<< " indicates javascript but given TemplateContext"
|
||||
|
@ -489,15 +490,15 @@ static TemplateContext GetTemplateContextFromPragma(
|
|||
const string* context = pragma.GetAttributeValue("context");
|
||||
if (context == NULL)
|
||||
return TC_MANUAL;
|
||||
if (*context == "HTML")
|
||||
if (*context == "HTML" || *context == "html")
|
||||
return TC_HTML;
|
||||
else if (*context == "JAVASCRIPT")
|
||||
else if (*context == "JAVASCRIPT" || *context == "javascript")
|
||||
return TC_JS;
|
||||
else if (*context == "CSS")
|
||||
else if (*context == "CSS" || *context == "css")
|
||||
return TC_CSS;
|
||||
else if (*context == "JSON")
|
||||
else if (*context == "JSON" || *context == "json")
|
||||
return TC_JSON;
|
||||
else if (*context == "XML")
|
||||
else if (*context == "XML" || *context == "xml")
|
||||
return TC_XML;
|
||||
return TC_MANUAL;
|
||||
}
|
||||
|
@ -1940,7 +1941,8 @@ TemplateToken SectionTemplateNode::GetNextToken(Template *my_template) {
|
|||
const string* parser_state = pragma.GetAttributeValue("state");
|
||||
bool in_tag = false;
|
||||
if (parser_state != NULL) {
|
||||
if (context == TC_HTML && *parser_state == "IN_TAG")
|
||||
if (context == TC_HTML && (*parser_state == "IN_TAG" ||
|
||||
*parser_state == "in_tag"))
|
||||
in_tag = true;
|
||||
else if (*parser_state != "default")
|
||||
FAIL("Unsupported state '" + *parser_state +
|
||||
|
|
|
@ -311,8 +311,10 @@ TemplateCache::RefcountedTemplate* TemplateCache::GetTemplateLocked(
|
|||
// Create a new template, insert it into the cache under
|
||||
// template_cache_key, and DecRef() the old one to indicate
|
||||
// the cache no longer has a reference to it.
|
||||
it->second.refcounted_tpl->DecRef();
|
||||
const Template* tpl = new Template(filename, strip, this);
|
||||
// DecRef after creating the new template since DecRef may free up
|
||||
// the storage for filename,
|
||||
it->second.refcounted_tpl->DecRef();
|
||||
it->second = CachedTemplate(tpl, CachedTemplate::FILE_BASED);
|
||||
}
|
||||
it->second.should_reload = false;
|
||||
|
@ -670,10 +672,7 @@ void TemplateCache::ReloadAllIfChanged(ReloadType reload_type) {
|
|||
it->second.should_reload = true;
|
||||
if (reload_type == IMMEDIATE_RELOAD) {
|
||||
const Template* tpl = it->second.refcounted_tpl->tpl();
|
||||
// TODO(csilvers): have template_file() return a TemplateString?
|
||||
TemplateCacheKey template_cache_key = TemplateCacheKey(
|
||||
TemplateString(tpl->template_file()).GetGlobalId(), tpl->strip());
|
||||
GetTemplateLocked(tpl->template_file(), tpl->strip(), template_cache_key);
|
||||
GetTemplateLocked(tpl->template_file(), tpl->strip(), it->first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,9 +87,8 @@ template<class T> void ArenaAllocator<T>::deallocate(pointer p, size_type n) {
|
|||
arena_->Free(p, n * sizeof(T));
|
||||
}
|
||||
|
||||
// TODO(csilvers): sizeof(void*) may be too big. But is probably ok.
|
||||
/*static*/ template<class T> const int ArenaAllocator<T>::kAlignment =
|
||||
(1 == sizeof(T) ? 1 : sizeof(void*));
|
||||
(1 == sizeof(T) ? 1 : BaseArena::kDefaultAlignment);
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// TemplateDictionary::map_arena_init
|
||||
|
@ -123,7 +122,8 @@ inline void TemplateDictionary::LazilyCreateDict(T** dict) {
|
|||
if (*dict != NULL)
|
||||
return;
|
||||
// Placement new: construct the map in the memory used by *dict.
|
||||
void* buffer = arena_->AllocAligned(sizeof(**dict), sizeof(void*));
|
||||
void* buffer = arena_->AllocAligned(sizeof(**dict),
|
||||
BaseArena::kDefaultAlignment);
|
||||
new (buffer) T(arena_);
|
||||
*dict = reinterpret_cast<T*>(buffer);
|
||||
}
|
||||
|
@ -138,7 +138,8 @@ inline void TemplateDictionary::LazyCreateTemplateGlobalDict() {
|
|||
}
|
||||
|
||||
inline TemplateDictionary::DictVector* TemplateDictionary::CreateDictVector() {
|
||||
void* buffer = arena_->AllocAligned(sizeof(DictVector), sizeof(void*));
|
||||
void* buffer = arena_->AllocAligned(sizeof(DictVector),
|
||||
BaseArena::kDefaultAlignment);
|
||||
// Placement new: construct the vector in the memory used by buffer.
|
||||
new (buffer) DictVector(arena_);
|
||||
return reinterpret_cast<DictVector*>(buffer);
|
||||
|
@ -149,7 +150,8 @@ inline TemplateDictionary* TemplateDictionary::CreateTemplateSubdict(
|
|||
UnsafeArena* arena,
|
||||
TemplateDictionary* parent_dict,
|
||||
TemplateDictionary* template_global_dict_owner) {
|
||||
void* buffer = arena->AllocAligned(sizeof(TemplateDictionary), sizeof(void*));
|
||||
void* buffer = arena->AllocAligned(sizeof(TemplateDictionary),
|
||||
BaseArena::kDefaultAlignment);
|
||||
// Placement new: construct the sub-tpl in the memory used by tplbuf.
|
||||
new (buffer) TemplateDictionary(name, arena, parent_dict,
|
||||
template_global_dict_owner);
|
||||
|
|
|
@ -102,7 +102,9 @@ const TemplateNamelist::MissingListType& TemplateNamelist::GetMissingList(
|
|||
const string path = Template::FindTemplateFilename(*iter);
|
||||
if (path.empty() || access(path.c_str(), R_OK) != 0) {
|
||||
missing_list_->push_back(*iter);
|
||||
std::cerr << "ERROR: Template file missing: " << path << std::endl;
|
||||
std::cerr << "ERROR: Template file missing: " << *iter
|
||||
<< " at path: " << (path.empty() ? "(empty path)" : path)
|
||||
<< std::endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include "config.h"
|
||||
#include <string>
|
||||
#include <ctype.h> // for isalpha, used on windows
|
||||
#include <string.h> // for strchr
|
||||
#include <ctemplate/template_pathops.h>
|
||||
|
||||
using std::string;
|
||||
|
@ -109,5 +110,37 @@ string Basename(const string& path) {
|
|||
return path; // no path-separator found, so whole string is the basename
|
||||
}
|
||||
|
||||
bool ContainsFullWord(const string& text, const string& word) {
|
||||
// List of delimiter characters to be considered. Please update the comment in
|
||||
// the header file if you change this list.
|
||||
static const char* delim = ".,_-#*?:";
|
||||
|
||||
const int inputlength = text.length();
|
||||
const int wordlength = word.length();
|
||||
|
||||
// corner cases
|
||||
if (inputlength == 0 || wordlength == 0 || wordlength > inputlength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int nextmatchpos = 0; // position from where search in the input string
|
||||
while (nextmatchpos < inputlength) {
|
||||
const int pos = text.find(word, nextmatchpos);
|
||||
if (pos == string::npos) {
|
||||
return false; // no match at all
|
||||
}
|
||||
|
||||
// if found, check that it is surrounded by delimiter characters.
|
||||
bool pre_delimited = (pos == 0) ||
|
||||
(strchr(delim, text.at(pos - 1)) != NULL);
|
||||
bool post_delimited = (pos >= inputlength - wordlength) ||
|
||||
(strchr(delim, text.at(pos + wordlength)) != NULL);
|
||||
if (pre_delimited && post_delimited) return true;
|
||||
|
||||
nextmatchpos = (pos + wordlength + 1);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
_END_GOOGLE_NAMESPACE_
|
||||
|
|
|
@ -216,15 +216,12 @@ struct TemplateStringHasher {
|
|||
assert(IsTemplateIdInitialized(id_b));
|
||||
return hasher(id_a, id_b);
|
||||
}
|
||||
// static makes this compile under MSVC (shrug)
|
||||
static const TemplateIdHasher hasher;
|
||||
TemplateIdHasher hasher;
|
||||
// These two public members are required by msvc. 4 and 8 are defaults.
|
||||
static const size_t bucket_size = 4;
|
||||
static const size_t min_buckets = 8;
|
||||
};
|
||||
|
||||
/*static*/ const TemplateIdHasher TemplateStringHasher::hasher = {};
|
||||
|
||||
namespace {
|
||||
Mutex mutex(Mutex::LINKER_INITIALIZED);
|
||||
|
||||
|
|
|
@ -1956,6 +1956,12 @@ class TemplateUnittest {
|
|||
expected_mods = "URL:U=html\nCLASS:h:H=attribute\nCOLOR:c\n";
|
||||
AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
|
||||
|
||||
text = "{{%AUTOESCAPE context=\"HTML\" state=\"in_tag\"}}" // lowercase ok
|
||||
"href=\"{{URL}}\" class={{CLASS:h}} style=\"font:{{COLOR}}\"";
|
||||
ASSERT(tpl = StringToTemplate(text, strip));
|
||||
expected_mods = "URL:U=html\nCLASS:h:H=attribute\nCOLOR:c\n";
|
||||
AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
|
||||
|
||||
// Repeat the test with trailing HTML that closes the tag. This is
|
||||
// undefined behavior. We test it to ensure the parser does not choke.
|
||||
text += ">Hello</a><span>Some text</span></body></html>";
|
||||
|
@ -1987,6 +1993,12 @@ class TemplateUnittest {
|
|||
expected_mods = "VAL:xml_escape\nDATA:h\n";
|
||||
AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
|
||||
|
||||
text = "{{%AUTOESCAPE context=\"xml\"}}" // lower-case XML
|
||||
"<PARAM name=\"{{VAL}}\">{{DATA:h}}";
|
||||
ASSERT(tpl = StringToTemplate(text, strip));
|
||||
expected_mods = "VAL:xml_escape\nDATA:h\n";
|
||||
AssertCorrectModifiersInTemplate(tpl, text, expected_mods);
|
||||
|
||||
text = "{{!bla}}{{%AUTOESCAPE context=\"HTML\"}}"; // after comment
|
||||
ASSERT(tpl = StringToTemplate(text, strip));
|
||||
text = "{{%AUTOESCAPE context=\"HTML\" state=\"default\"}}";
|
||||
|
@ -2034,7 +2046,7 @@ class TemplateUnittest {
|
|||
ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
|
||||
text = "{{%AUTOESCAPE context=\"HTML\" }}"; // extra whitesp
|
||||
ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
|
||||
text = "{{%AUTOESCAPE context=\"xml\"}}"; // lower-case xml
|
||||
text = "{{%AUTOESCAPE context=\"Xml\"}}"; // mixed-case xml
|
||||
ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
|
||||
text = "{{%AUTOESCAPE context=\"HTML\" state=\"tag\"}}"; // bad state
|
||||
ASSERT((tpl = StringToTemplate(text, strip)) == NULL);
|
||||
|
|
|
@ -57,6 +57,14 @@ bool CTEMPLATE_DLL_DECL IsDirectory(const std::string& path); // ends in "/"?
|
|||
void CTEMPLATE_DLL_DECL NormalizeDirectory(std::string* dir); // appends /
|
||||
std::string CTEMPLATE_DLL_DECL Basename(const std::string& path);
|
||||
|
||||
// Returns true iff text contains the word as a full word, i.e. delimited by one
|
||||
// of [.,_-#*?:] on both the sides.
|
||||
// This is used while loading a template, to check that the file's name matches
|
||||
// the auto-escape mode specified by it.
|
||||
// NOTE: This assumes that the word doesn't contain any of the delimiter
|
||||
// characters.
|
||||
bool CTEMPLATE_DLL_DECL ContainsFullWord(const std::string& text, const std::string& word);
|
||||
|
||||
}
|
||||
|
||||
#endif // TEMPLATE_TEMPLATE_PATHOPS_H_
|
||||
|
|
Loading…
Reference in New Issue
Block a user